mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
同步更新,调整资源工厂逻辑,优化EzPro皮肤逻辑
This commit is contained in:
@@ -50,7 +50,7 @@
|
|||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<Company>ppy Pty Ltd</Company>
|
<Company>ppy Pty Ltd</Company>
|
||||||
<Copyright>Copyright (c) 2024 ppy Pty Ltd</Copyright>
|
<Copyright>Copyright (c) 2025 ppy Pty Ltd</Copyright>
|
||||||
<PackageTags>osu game</PackageTags>
|
<PackageTags>osu game</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
2
LICENCE
2
LICENCE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2024 ppy Pty Ltd <contact@ppy.sh>.
|
Copyright (c) 2025 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PackageProjectUrl>https://github.com/ppy/osu/blob/master/Templates</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/ppy/osu/blob/master/Templates</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<copyright>Copyright (c) 2024 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
|
||||||
<Description>Templates to use when creating a ruleset for consumption in osu!.</Description>
|
<Description>Templates to use when creating a ruleset for consumption in osu!.</Description>
|
||||||
<PackageTags>dotnet-new;templates;osu</PackageTags>
|
<PackageTags>dotnet-new;templates;osu</PackageTags>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.704.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.718.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Microsoft.Maui.Devices;
|
using Microsoft.Maui.Devices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
@@ -21,58 +23,30 @@ namespace osu.Android
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OsuGameActivity gameActivity;
|
private readonly OsuGameActivity gameActivity;
|
||||||
|
|
||||||
|
private readonly PackageInfo packageInfo;
|
||||||
|
|
||||||
public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
public override Vector2 ScalingContainerTargetDrawSize => new Vector2(1024, 1024 * DrawHeight / DrawWidth);
|
||||||
|
|
||||||
public OsuGameAndroid(OsuGameActivity activity)
|
public OsuGameAndroid(OsuGameActivity activity)
|
||||||
: base(null)
|
: base(null)
|
||||||
{
|
{
|
||||||
gameActivity = activity;
|
gameActivity = activity;
|
||||||
|
packageInfo = Application.Context.ApplicationContext!.PackageManager!.GetPackageInfo(Application.Context.ApplicationContext.PackageName!, 0).AsNonNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Version AssemblyVersion
|
public override string Version
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var packageInfo = Application.Context.ApplicationContext!.PackageManager!.GetPackageInfo(Application.Context.ApplicationContext.PackageName!, 0).AsNonNull();
|
if (!IsDeployedBuild)
|
||||||
|
return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release");
|
||||||
|
|
||||||
try
|
return packageInfo.VersionName.AsNonNull();
|
||||||
{
|
|
||||||
// We store the osu! build number in the "VersionCode" field to better support google play releases.
|
|
||||||
// If we were to use the main build number, it would require a new submission each time (similar to TestFlight).
|
|
||||||
// In order to do this, we should split it up and pad the numbers to still ensure sequential increase over time.
|
|
||||||
//
|
|
||||||
// We also need to be aware that older SDK versions store this as a 32bit int.
|
|
||||||
//
|
|
||||||
// Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
|
|
||||||
string versionName;
|
|
||||||
|
|
||||||
if (OperatingSystem.IsAndroidVersionAtLeast(28))
|
|
||||||
{
|
|
||||||
versionName = packageInfo.LongVersionCode.ToString();
|
|
||||||
// ensure we only read the trailing portion of long (the part we are interested in).
|
|
||||||
versionName = versionName.Substring(versionName.Length - 9);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
|
||||||
// this is required else older SDKs will report missing method exception.
|
|
||||||
versionName = packageInfo.VersionCode.ToString();
|
|
||||||
#pragma warning restore CS0618 // Type or member is obsolete
|
|
||||||
}
|
|
||||||
|
|
||||||
// undo play store version garbling (as mentioned above).
|
|
||||||
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Version(packageInfo.VersionName.AsNonNull());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Version AssemblyVersion => new Version(packageInfo.VersionName.AsNonNull().Split('-').First());
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
{
|
{
|
||||||
"solution": {
|
"solution": {
|
||||||
"path": "osu.sln",
|
"path": "osu.sln",
|
||||||
"projects": [
|
"projects": [
|
||||||
"..\\osu-resources\\osu.Game.Resources\\osu.Game.Resources.csproj",
|
"..\\osu-framework\\osu.Framework.NativeLibs\\osu.Framework.NativeLibs.csproj",
|
||||||
"osu.Desktop\\osu.Desktop.csproj",
|
"..\\osu-framework\\osu.Framework\\osu.Framework.csproj",
|
||||||
"osu.Game.Benchmarks\\osu.Game.Benchmarks.csproj",
|
"..\\osu-resources\\osu.Game.Resources\\osu.Game.Resources.csproj",
|
||||||
"osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj",
|
"osu.Desktop\\osu.Desktop.csproj",
|
||||||
"osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
|
"osu.Game.Benchmarks\\osu.Game.Benchmarks.csproj",
|
||||||
"osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj",
|
"osu.Game.Rulesets.Catch.Tests\\osu.Game.Rulesets.Catch.Tests.csproj",
|
||||||
"osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj",
|
"osu.Game.Rulesets.Catch\\osu.Game.Rulesets.Catch.csproj",
|
||||||
"osu.Game.Rulesets.Osu.Tests\\osu.Game.Rulesets.Osu.Tests.csproj",
|
"osu.Game.Rulesets.Mania.Tests\\osu.Game.Rulesets.Mania.Tests.csproj",
|
||||||
"osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj",
|
"osu.Game.Rulesets.Mania\\osu.Game.Rulesets.Mania.csproj",
|
||||||
"osu.Game.Rulesets.Taiko.Tests\\osu.Game.Rulesets.Taiko.Tests.csproj",
|
"osu.Game.Rulesets.Osu.Tests\\osu.Game.Rulesets.Osu.Tests.csproj",
|
||||||
"osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj",
|
"osu.Game.Rulesets.Osu\\osu.Game.Rulesets.Osu.csproj",
|
||||||
"osu.Game.Tests\\osu.Game.Tests.csproj",
|
"osu.Game.Rulesets.Taiko.Tests\\osu.Game.Rulesets.Taiko.Tests.csproj",
|
||||||
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
"osu.Game.Rulesets.Taiko\\osu.Game.Rulesets.Taiko.csproj",
|
||||||
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
"osu.Game.Tests\\osu.Game.Tests.csproj",
|
||||||
"osu.Game\\osu.Game.csproj",
|
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
"osu.Game\\osu.Game.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
||||||
"../osu-framework/osu.Framework/osu.Framework.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
"../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
public override bool RestartAppWhenExited()
|
public override bool RestartAppWhenExited()
|
||||||
{
|
{
|
||||||
Task.Run(() => Velopack.UpdateExe.Start()).FireAndForget();
|
Task.Run(() => Velopack.UpdateExe.Start(waitPid: (uint)Environment.ProcessId)).FireAndForget();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,33 +53,44 @@ namespace osu.Desktop.Updater
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IUpdateSource updateSource = new GithubSource(@"https://github.com/ppy/osu", null, ReleaseStream.Value == Game.Configuration.ReleaseStream.Tachyon);
|
try
|
||||||
Velopack.UpdateManager updateManager = new Velopack.UpdateManager(updateSource, new UpdateOptions
|
|
||||||
{
|
{
|
||||||
AllowVersionDowngrade = true
|
IUpdateSource updateSource = new GithubSource(@"https://github.com/ppy/osu", null, ReleaseStream.Value == Game.Configuration.ReleaseStream.Tachyon);
|
||||||
});
|
Velopack.UpdateManager updateManager = new Velopack.UpdateManager(updateSource, new UpdateOptions
|
||||||
|
{
|
||||||
|
AllowVersionDowngrade = true
|
||||||
|
});
|
||||||
|
|
||||||
UpdateInfo? update = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
UpdateInfo? update = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
log("Update check cancelled");
|
||||||
|
scheduleNextUpdateCheck();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update == null)
|
||||||
|
{
|
||||||
|
// No update is available.
|
||||||
|
log("No update found");
|
||||||
|
scheduleNextUpdateCheck();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download update in the background while notifying awaiters of the update being available.
|
||||||
|
log($"New update available: {update.TargetFullRelease.Version}");
|
||||||
|
downloadUpdate(updateManager, update, cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
log("Update check cancelled");
|
log($"Update check failed with error ({e.Message})");
|
||||||
|
|
||||||
|
// we shouldn't crash on a web failure. or any failure for the matter.
|
||||||
scheduleNextUpdateCheck();
|
scheduleNextUpdateCheck();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update == null)
|
|
||||||
{
|
|
||||||
// No update is available.
|
|
||||||
log("No update found");
|
|
||||||
scheduleNextUpdateCheck();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download update in the background while notifying awaiters of the update being available.
|
|
||||||
log($"New update available: {update.TargetFullRelease.Version}");
|
|
||||||
downloadUpdate(updateManager, update, cancellationToken);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void downloadUpdate(Velopack.UpdateManager updateManager, UpdateInfo update, CancellationToken cancellationToken) => Task.Run(async () =>
|
private void downloadUpdate(Velopack.UpdateManager updateManager, UpdateInfo update, CancellationToken cancellationToken) => Task.Run(async () =>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||||
<releaseNotes>testing</releaseNotes>
|
<releaseNotes>testing</releaseNotes>
|
||||||
<copyright>Copyright (c) 2024 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2025 ppy Pty Ltd</copyright>
|
||||||
<language>en-AU</language>
|
<language>en-AU</language>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
var ruleset = new CatchRuleset();
|
var ruleset = new CatchRuleset();
|
||||||
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
var ruleset = new CatchRuleset();
|
var ruleset = new CatchRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModHalfTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
var ruleset = new CatchRuleset();
|
var ruleset = new CatchRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new CatchModDoubleTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
@@ -265,9 +266,10 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <seealso cref="CatchHitObject.ApplyDefaultsToSelf"/>
|
/// <seealso cref="CatchHitObject.ApplyDefaultsToSelf"/>
|
||||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
|
||||||
{
|
{
|
||||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||||
|
double rate = ModUtils.CalculateRateWithMods(mods);
|
||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, CatchHitObject.PREEMPT_MAX, CatchHitObject.PREEMPT_MID, CatchHitObject.PREEMPT_MIN);
|
||||||
preempt /= rate;
|
preempt /= rate;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
@@ -9,5 +10,12 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public class CatchModEasy : ModEasyWithExtraLives
|
public class CatchModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and extra lives!";
|
public override LocalisableString Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and extra lives!";
|
||||||
|
|
||||||
|
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty *= ADJUST_RATIO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
base.ApplyToDifficulty(difficulty);
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
|
||||||
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
|
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
|
||||||
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
|
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@@ -38,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
|
new object[] { LegacyMods.Key2, new[] { typeof(ManiaModKey2) } },
|
||||||
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
|
new object[] { LegacyMods.Mirror, new[] { typeof(ManiaModMirror) } },
|
||||||
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } },
|
new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(ManiaModHardRock), typeof(ManiaModDoubleTime) } },
|
||||||
new object[] { LegacyMods.ScoreV2, new[] { typeof(ModScoreV2) } },
|
new object[] { LegacyMods.ScoreV2, new[] { typeof(ManiaModScoreV2) } },
|
||||||
};
|
};
|
||||||
|
|
||||||
[TestCaseSource(nameof(mania_mod_mapping))]
|
[TestCaseSource(nameof(mania_mod_mapping))]
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Mods;
|
using osu.Game.Rulesets.Mania.Mods;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@@ -521,14 +520,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
ScoreInfo = new ScoreInfo
|
ScoreInfo = new ScoreInfo
|
||||||
{
|
{
|
||||||
Ruleset = CreateRuleset().RulesetInfo,
|
Ruleset = CreateRuleset().RulesetInfo,
|
||||||
Mods = [new ModScoreV2()]
|
Mods = [new ManiaModScoreV2()]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
|
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
@@ -556,7 +554,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_convert_test_cases))]
|
[TestCaseSource(nameof(score_v1_convert_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
@@ -585,7 +582,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))]
|
[TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
@@ -614,7 +610,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))]
|
[TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
@@ -643,7 +638,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))]
|
[TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
@@ -672,7 +666,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
|
||||||
[TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))]
|
[TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))]
|
||||||
public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.LAsEZMania
|
|||||||
float width = (float)columnWidth.Value;
|
float width = (float)columnWidth.Value;
|
||||||
int index = KeyFlow.IndexOf(counter);
|
int index = KeyFlow.IndexOf(counter);
|
||||||
|
|
||||||
if (ezSkinConfig.GetColumnType(StageDefinition.Columns, index) == "S1")
|
if (ezSkinConfig.GetColumnType(StageDefinition.Columns, index) == "S")
|
||||||
width *= (float)specialFactor.Value;
|
width *= (float)specialFactor.Value;
|
||||||
|
|
||||||
counter.Width = width;
|
counter.Width = width;
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
yield return new ManiaModMirror();
|
yield return new ManiaModMirror();
|
||||||
|
|
||||||
if (mods.HasFlag(LegacyMods.ScoreV2))
|
if (mods.HasFlag(LegacyMods.ScoreV2))
|
||||||
yield return new ModScoreV2();
|
yield return new ManiaModScoreV2();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
|
||||||
@@ -356,7 +356,7 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
case ModType.System:
|
case ModType.System:
|
||||||
return new Mod[]
|
return new Mod[]
|
||||||
{
|
{
|
||||||
new ModScoreV2(),
|
new ManiaModScoreV2(),
|
||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -490,6 +490,32 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
return statistics.ToArray();
|
return statistics.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <seealso cref="ManiaHitWindows"/>
|
||||||
|
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
|
||||||
|
{
|
||||||
|
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||||
|
|
||||||
|
// notably, in mania, hit windows are designed to be independent of track playback rate (see `ManiaHitWindows.SpeedMultiplier`).
|
||||||
|
// *however*, to not make matters *too* simple, mania Hard Rock and Easy differ from all other rulesets
|
||||||
|
// in that they apply multipliers *to hit window durations directly* rather than to the Overall Difficulty attribute itself.
|
||||||
|
// because the duration of hit window durations as a function of OD is not a linear function,
|
||||||
|
// this means that multiplying the OD is *not* the same thing as multiplying the hit window duration.
|
||||||
|
// in fact, the second operation is *much* harsher and will produce values much farther outside of normal operating range
|
||||||
|
// (even negative in the case of Easy).
|
||||||
|
// stable handles this wrong on song select and just assumes that it can handle mania EZ / HR the same way as all other rulesets.
|
||||||
|
|
||||||
|
double perfectHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, ManiaHitWindows.PERFECT_WINDOW_RANGE);
|
||||||
|
|
||||||
|
if (mods.Any(m => m is ManiaModHardRock))
|
||||||
|
perfectHitWindow /= ManiaModHardRock.HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
else if (mods.Any(m => m is ManiaModEasy))
|
||||||
|
perfectHitWindow /= ManiaModEasy.HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
|
||||||
|
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(perfectHitWindow, ManiaHitWindows.PERFECT_WINDOW_RANGE);
|
||||||
|
|
||||||
|
return adjustedDifficulty;
|
||||||
|
}
|
||||||
|
|
||||||
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
|
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
|
||||||
{
|
{
|
||||||
return new ManiaFilterCriteria();
|
return new ManiaFilterCriteria();
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
@@ -17,29 +15,21 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Historically, in osu!mania, hit windows are expected to adjust relative to the gameplay rate such that the real-world hit window remains the same.
|
/// Historically, in osu!mania, hit windows are expected to adjust relative to the gameplay rate such that the real-world hit window remains the same.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface IManiaRateAdjustmentMod : IApplicableToDifficulty, IApplicableToHitObject
|
public interface IManiaRateAdjustmentMod : IApplicableToHitObject
|
||||||
{
|
{
|
||||||
BindableNumber<double> SpeedChange { get; }
|
BindableNumber<double> SpeedChange { get; }
|
||||||
|
|
||||||
HitWindows HitWindows { get; set; }
|
|
||||||
|
|
||||||
void IApplicableToDifficulty.ApplyToDifficulty(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
HitWindows = new ManiaHitWindows(SpeedChange.Value);
|
|
||||||
HitWindows.SetDifficulty(difficulty.OverallDifficulty);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
|
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case Note:
|
case Note:
|
||||||
hitObject.HitWindows = HitWindows;
|
((ManiaHitWindows)hitObject.HitWindows).SpeedMultiplier = SpeedChange.Value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case HoldNote hold:
|
case HoldNote hold:
|
||||||
hold.Head.HitWindows = HitWindows;
|
((ManiaHitWindows)hold.Head.HitWindows).SpeedMultiplier = SpeedChange.Value;
|
||||||
hold.Tail.HitWindows = HitWindows;
|
((ManiaHitWindows)hold.Tail.HitWindows).SpeedMultiplier = SpeedChange.Value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Mods.LAsMods
|
|||||||
public override string Acronym => "SB";
|
public override string Acronym => "SB";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
public override LocalisableString Description => "LaMod: Hold the keys. Adjust the gaps.";
|
public override LocalisableString Description => "LaMod: Full LN 面海, 可调面缝";
|
||||||
|
|
||||||
public override ModType Type => ModType.CustomMod;
|
public override ModType Type => ModType.CustomMod;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Acronym => Name;
|
public override string Acronym => Name;
|
||||||
public abstract int KeyCount { get; }
|
public abstract int KeyCount { get; }
|
||||||
public override ModType Type => ModType.Conversion;
|
public override ModType Type => ModType.Conversion;
|
||||||
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
|
public override double ScoreMultiplier => 0.9;
|
||||||
public override bool Ranked => UsesDefaultConfiguration;
|
public override bool Ranked => UsesDefaultConfiguration;
|
||||||
|
|
||||||
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
public void ApplyToBeatmapConverter(IBeatmapConverter beatmapConverter)
|
||||||
|
|||||||
@@ -1,11 +1,41 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModClassic : ModClassic
|
public class ManiaModClassic : ModClassic, IApplicableToBeatmap
|
||||||
{
|
{
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
bool isConvert = !beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
|
||||||
|
|
||||||
|
foreach (var ho in beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
switch (ho)
|
||||||
|
{
|
||||||
|
case Note note:
|
||||||
|
{
|
||||||
|
var hitWindows = (ManiaHitWindows)note.HitWindows;
|
||||||
|
hitWindows.IsConvert = isConvert;
|
||||||
|
hitWindows.ClassicModActive = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HoldNote hold:
|
||||||
|
{
|
||||||
|
var headWindows = (ManiaHitWindows)hold.Head.HitWindows;
|
||||||
|
var tailWindows = (ManiaHitWindows)hold.Tail.HitWindows;
|
||||||
|
headWindows.IsConvert = tailWindows.IsConvert = isConvert;
|
||||||
|
headWindows.ClassicModActive = tailWindows.ClassicModActive = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModDaycore : ModDaycore, IManiaRateAdjustmentMod
|
public class ManiaModDaycore : ModDaycore, IManiaRateAdjustmentMod
|
||||||
{
|
{
|
||||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
|
public class ManiaModDoubleTime : ModDoubleTime, IManiaRateAdjustmentMod
|
||||||
{
|
{
|
||||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
|
||||||
|
|
||||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||||
// make the map harder and is more of a personal preference.
|
// make the map harder and is more of a personal preference.
|
||||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||||
|
|||||||
@@ -2,12 +2,32 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModEasy : ModEasyWithExtraLives
|
public class ManiaModEasy : ModEasyWithExtraLives, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"More forgiving HP drain, less accuracy required, and extra lives!";
|
public override LocalisableString Description => @"More forgiving HP drain, less accuracy required, and extra lives!";
|
||||||
|
|
||||||
|
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1 / 1.4;
|
||||||
|
|
||||||
|
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Note:
|
||||||
|
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HoldNote hold:
|
||||||
|
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModHalfTime : ModHalfTime, IManiaRateAdjustmentMod
|
public class ManiaModHalfTime : ModHalfTime, IManiaRateAdjustmentMod
|
||||||
{
|
{
|
||||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModHardRock : ModHardRock
|
public class ManiaModHardRock : ModHardRock, IApplicableToHitObject
|
||||||
{
|
{
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override bool Ranked => false;
|
public override bool Ranked => false;
|
||||||
|
|
||||||
|
public const double HIT_WINDOW_DIFFICULTY_MULTIPLIER = 1.4;
|
||||||
|
|
||||||
|
void IApplicableToHitObject.ApplyToHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Note:
|
||||||
|
((ManiaHitWindows)hitObject.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HoldNote hold:
|
||||||
|
((ManiaHitWindows)hold.Head.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
((ManiaHitWindows)hold.Tail.HitWindows).DifficultyMultiplier = HIT_WINDOW_DIFFICULTY_MULTIPLIER;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
|
public class ManiaModNightcore : ModNightcore<ManiaHitObject>, IManiaRateAdjustmentMod
|
||||||
{
|
{
|
||||||
public HitWindows HitWindows { get; set; } = new ManiaHitWindows();
|
|
||||||
|
|
||||||
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
// For now, all rate-increasing mods should be given a 1x multiplier in mania because it doesn't always
|
||||||
// make the map any harder and is more of a personal preference.
|
// make the map any harder and is more of a personal preference.
|
||||||
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
// In the future, we can consider adjusting this by experimenting with not applying the hitwindow leniency.
|
||||||
|
|||||||
37
osu.Game.Rulesets.Mania/Mods/ManiaModScoreV2.cs
Normal file
37
osu.Game.Rulesets.Mania/Mods/ManiaModScoreV2.cs
Normal file
@@ -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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
|
{
|
||||||
|
public class ManiaModScoreV2 : ModScoreV2, IApplicableToBeatmap
|
||||||
|
{
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
foreach (var ho in beatmap.HitObjects)
|
||||||
|
{
|
||||||
|
switch (ho)
|
||||||
|
{
|
||||||
|
case Note note:
|
||||||
|
{
|
||||||
|
var hitWindows = (ManiaHitWindows)note.HitWindows;
|
||||||
|
hitWindows.ScoreV2Active = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case HoldNote hold:
|
||||||
|
{
|
||||||
|
var headWindows = (ManiaHitWindows)hold.Head.HitWindows;
|
||||||
|
var tailWindows = (ManiaHitWindows)hold.Tail.HitWindows;
|
||||||
|
headWindows.ScoreV2Active = tailWindows.ScoreV2Active = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,14 +9,91 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
{
|
{
|
||||||
public class ManiaHitWindows : HitWindows
|
public class ManiaHitWindows : HitWindows
|
||||||
{
|
{
|
||||||
private static readonly DifficultyRange perfect_window_range = new DifficultyRange(22.4D, 19.4D, 13.9D);
|
public static readonly DifficultyRange PERFECT_WINDOW_RANGE = new DifficultyRange(22.4D, 19.4D, 13.9D);
|
||||||
private static readonly DifficultyRange great_window_range = new DifficultyRange(64, 49, 34);
|
private static readonly DifficultyRange great_window_range = new DifficultyRange(64, 49, 34);
|
||||||
private static readonly DifficultyRange good_window_range = new DifficultyRange(97, 82, 67);
|
private static readonly DifficultyRange good_window_range = new DifficultyRange(97, 82, 67);
|
||||||
private static readonly DifficultyRange ok_window_range = new DifficultyRange(127, 112, 97);
|
private static readonly DifficultyRange ok_window_range = new DifficultyRange(127, 112, 97);
|
||||||
private static readonly DifficultyRange meh_window_range = new DifficultyRange(151, 136, 121);
|
private static readonly DifficultyRange meh_window_range = new DifficultyRange(151, 136, 121);
|
||||||
private static readonly DifficultyRange miss_window_range = new DifficultyRange(188, 173, 158);
|
private static readonly DifficultyRange miss_window_range = new DifficultyRange(188, 173, 158);
|
||||||
|
|
||||||
private readonly double multiplier;
|
private double speedMultiplier = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier used to compensate for the playback speed of the track speeding up or slowing down.
|
||||||
|
/// The goal of this multiplier is to keep hit windows independent of track speed.
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>When the track speed is above 1, the hit window ranges are multiplied by <see cref="SpeedMultiplier"/>, because the time elapses faster.</item>
|
||||||
|
/// <item>When the track speed is below 1, the hit window ranges are also multiplied by <see cref="SpeedMultiplier"/>, because the time elapses slower.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public double SpeedMultiplier
|
||||||
|
{
|
||||||
|
get => speedMultiplier;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
speedMultiplier = value;
|
||||||
|
updateWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double difficultyMultiplier = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multiplier used to make the gameplay more or less difficult.
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item>When the <see cref="DifficultyMultiplier"/> is above 1, the hit windows decrease to make the gameplay harder.</item>
|
||||||
|
/// <item>When the <see cref="DifficultyMultiplier"/> is below 1, the hit windows increase to make the gameplay easier.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public double DifficultyMultiplier
|
||||||
|
{
|
||||||
|
get => difficultyMultiplier;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
difficultyMultiplier = value;
|
||||||
|
updateWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double totalMultiplier => speedMultiplier / difficultyMultiplier;
|
||||||
|
|
||||||
|
private double overallDifficulty;
|
||||||
|
|
||||||
|
private bool classicModActive;
|
||||||
|
|
||||||
|
public bool ClassicModActive
|
||||||
|
{
|
||||||
|
get => classicModActive;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
classicModActive = value;
|
||||||
|
updateWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool scoreV2Active;
|
||||||
|
|
||||||
|
public bool ScoreV2Active
|
||||||
|
{
|
||||||
|
get => scoreV2Active;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
scoreV2Active = value;
|
||||||
|
updateWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool isConvert;
|
||||||
|
|
||||||
|
public bool IsConvert
|
||||||
|
{
|
||||||
|
get => isConvert;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
isConvert = value;
|
||||||
|
updateWindows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private double perfect;
|
private double perfect;
|
||||||
private double great;
|
private double great;
|
||||||
@@ -25,16 +102,6 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
private double meh;
|
private double meh;
|
||||||
private double miss;
|
private double miss;
|
||||||
|
|
||||||
public ManiaHitWindows()
|
|
||||||
: this(1)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ManiaHitWindows(double multiplier)
|
|
||||||
{
|
|
||||||
this.multiplier = multiplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool IsHitResultAllowed(HitResult result)
|
public override bool IsHitResultAllowed(HitResult result)
|
||||||
{
|
{
|
||||||
switch (result)
|
switch (result)
|
||||||
@@ -55,12 +122,44 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
|||||||
|
|
||||||
public override void SetDifficulty(double difficulty)
|
public override void SetDifficulty(double difficulty)
|
||||||
{
|
{
|
||||||
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, perfect_window_range) * multiplier) + 0.5;
|
overallDifficulty = difficulty;
|
||||||
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, great_window_range) * multiplier) + 0.5;
|
updateWindows();
|
||||||
good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, good_window_range) * multiplier) + 0.5;
|
}
|
||||||
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, ok_window_range) * multiplier) + 0.5;
|
|
||||||
meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, meh_window_range) * multiplier) + 0.5;
|
private void updateWindows()
|
||||||
miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, miss_window_range) * multiplier) + 0.5;
|
{
|
||||||
|
if (ClassicModActive && !ScoreV2Active)
|
||||||
|
{
|
||||||
|
if (IsConvert)
|
||||||
|
{
|
||||||
|
perfect = Math.Floor(16 * totalMultiplier) + 0.5;
|
||||||
|
great = Math.Floor((Math.Round(overallDifficulty) > 4 ? 34 : 47) * totalMultiplier) + 0.5;
|
||||||
|
good = Math.Floor((Math.Round(overallDifficulty) > 4 ? 67 : 77) * totalMultiplier) + 0.5;
|
||||||
|
ok = Math.Floor(97 * totalMultiplier) + 0.5;
|
||||||
|
meh = Math.Floor(121 * totalMultiplier) + 0.5;
|
||||||
|
miss = Math.Floor(158 * totalMultiplier) + 0.5;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double invertedOd = Math.Clamp(10 - overallDifficulty, 0, 10);
|
||||||
|
|
||||||
|
perfect = Math.Floor(16 * totalMultiplier) + 0.5;
|
||||||
|
great = Math.Floor((34 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
||||||
|
good = Math.Floor((67 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
||||||
|
ok = Math.Floor((97 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
||||||
|
meh = Math.Floor((121 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
||||||
|
miss = Math.Floor((158 + 3 * invertedOd) * totalMultiplier) + 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, PERFECT_WINDOW_RANGE) * totalMultiplier) + 0.5;
|
||||||
|
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, great_window_range) * totalMultiplier) + 0.5;
|
||||||
|
good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, good_window_range) * totalMultiplier) + 0.5;
|
||||||
|
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, ok_window_range) * totalMultiplier) + 0.5;
|
||||||
|
meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, meh_window_range) * totalMultiplier) + 0.5;
|
||||||
|
miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(overallDifficulty, miss_window_range) * totalMultiplier) + 0.5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double WindowFor(HitResult result)
|
public override double WindowFor(HitResult result)
|
||||||
|
|||||||
@@ -2,44 +2,29 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens;
|
|
||||||
using osu.Game.Screens.LAsEzExtensions;
|
using osu.Game.Screens.LAsEzExtensions;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
public partial class EzHitExplosion : CompositeDrawable, IHitExplosion
|
public partial class EzHitExplosion : EzNoteBase, IHitExplosion
|
||||||
{
|
{
|
||||||
private TextureAnimation? animation;
|
protected override bool BoolUpdateColor => false;
|
||||||
private TextureAnimation? animationP;
|
|
||||||
private Container container = null!;
|
|
||||||
|
|
||||||
private IBindable<double> noteHeightBindable = new Bindable<double>();
|
|
||||||
private IBindable<double> columnWidthBindable = new Bindable<double>();
|
|
||||||
private IBindable<double> specialFactorBindable = new Bindable<double>();
|
|
||||||
|
|
||||||
// public override bool RemoveWhenNotAlive => true;
|
// public override bool RemoveWhenNotAlive => true;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Column column { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private StageDefinition stageDefinition { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EzLocalTextureFactory factory { get; set; } = null!;
|
private EzLocalTextureFactory factory { get; set; } = null!;
|
||||||
|
|
||||||
|
private TextureAnimation? primaryAnimation;
|
||||||
|
private TextureAnimation? goodAnimation;
|
||||||
|
private bool animationsCreated;
|
||||||
|
|
||||||
public EzHitExplosion()
|
public EzHitExplosion()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@@ -51,78 +36,54 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
|
OnDrawableChanged();
|
||||||
noteHeightBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
|
||||||
columnWidthBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.ColumnWidth);
|
|
||||||
specialFactorBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.SpecialFactor);
|
|
||||||
|
|
||||||
noteHeightBindable.BindValueChanged(_ => updateY(), true);
|
|
||||||
columnWidthBindable.BindValueChanged(_ => updateY(), true);
|
|
||||||
specialFactorBindable.BindValueChanged(_ => updateY(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void OnDrawableChanged()
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
onSkinChanged();
|
|
||||||
factory.OnNoteChanged += onSkinChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (isDisposing) { factory.OnNoteChanged -= onSkinChanged; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAnimation()
|
|
||||||
{
|
{
|
||||||
ClearInternal();
|
ClearInternal();
|
||||||
|
primaryAnimation = null;
|
||||||
|
goodAnimation = null;
|
||||||
|
|
||||||
animation = factory.CreateAnimation("noteflare");
|
primaryAnimation = factory.CreateAnimation("noteflare");
|
||||||
animationP = factory.CreateAnimation("noteflaregood");
|
goodAnimation = factory.CreateAnimation("noteflaregood");
|
||||||
|
|
||||||
container = new Container
|
if (primaryAnimation?.FrameCount > 0)
|
||||||
|
AddInternal(primaryAnimation);
|
||||||
|
|
||||||
|
if (goodAnimation?.FrameCount > 0)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
goodAnimation.Alpha = 0;
|
||||||
Origin = Anchor.BottomCentre,
|
AddInternal(goodAnimation);
|
||||||
RelativeSizeAxes = Axes.None,
|
}
|
||||||
Children = new Drawable[]
|
|
||||||
{ animation },
|
|
||||||
};
|
|
||||||
|
|
||||||
AddInternal(container);
|
animationsCreated = true;
|
||||||
updateY();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateY()
|
protected override void UpdateSize()
|
||||||
{
|
{
|
||||||
bool isSpecialColumn = ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1";
|
base.UpdateSize();
|
||||||
double columnWidth = columnWidthBindable.Value * (isSpecialColumn ? specialFactorBindable.Value : 1);
|
float moveY = NoteSize.Value.Y / 2;
|
||||||
|
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
float aspectRatio = factory.GetRatio("whitenote");
|
|
||||||
|
|
||||||
float moveY = isSquare
|
|
||||||
? (float)columnWidth / 2 * aspectRatio
|
|
||||||
: (float)noteHeightBindable.Value / 2 * aspectRatio;
|
|
||||||
|
|
||||||
// baseYPosition = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPosition.Value - moveY;
|
// baseYPosition = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPosition.Value - moveY;
|
||||||
Position = new Vector2(0, -moveY);
|
Position = new Vector2(0, -moveY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSkinChanged()
|
|
||||||
{
|
|
||||||
loadAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Animate(JudgementResult result)
|
public void Animate(JudgementResult result)
|
||||||
{
|
{
|
||||||
loadAnimation();
|
if (!animationsCreated) OnDrawableChanged();
|
||||||
|
|
||||||
if (result.Type > HitResult.Great)
|
if (primaryAnimation?.FrameCount > 0)
|
||||||
{
|
{
|
||||||
if (animationP != null) container.Add(animationP);
|
primaryAnimation.Alpha = 1;
|
||||||
|
primaryAnimation.GotoFrame(0);
|
||||||
|
primaryAnimation.Restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.Type >= HitResult.Great && goodAnimation?.FrameCount > 0)
|
||||||
|
{
|
||||||
|
goodAnimation.Alpha = 1;
|
||||||
|
goodAnimation.GotoFrame(0);
|
||||||
|
goodAnimation.Restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
internal partial class EzHitTarget : EzNote
|
internal partial class EzHitTarget : EzNote
|
||||||
{
|
{
|
||||||
// private IBindable<double> hitPosition = new Bindable<double>();
|
protected override bool BoolUpdateColor => false;
|
||||||
|
|
||||||
protected override bool ShowSeparators => false;
|
|
||||||
protected override bool UseColorization => false;
|
protected override bool UseColorization => false;
|
||||||
|
protected override bool ShowSeparators => false;
|
||||||
|
|
||||||
protected override string ColorPrefix => "white";
|
protected override string ColorPrefix => "white";
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@@ -23,9 +23,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IGameplayClock gameplayClock { get; set; } = null!;
|
private IGameplayClock gameplayClock { get; set; } = null!;
|
||||||
|
|
||||||
// [Resolved]
|
|
||||||
// private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
public EzHitTarget()
|
public EzHitTarget()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@@ -38,11 +35,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
// hitPosition = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
|
||||||
// hitPosition.BindValueChanged(_ => updateY(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private float baseYPosition = 0f;
|
|
||||||
private double beatInterval;
|
private double beatInterval;
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@@ -61,12 +55,5 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
double smoothValue = 0.3 * Math.Sin(progress * 2 * Math.PI);
|
double smoothValue = 0.3 * Math.Sin(progress * 2 * Math.PI);
|
||||||
Y = (float)(smoothValue * 6);
|
Y = (float)(smoothValue * 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
//DrawableManiaRuleset中关联设置后,此处不必设置
|
|
||||||
// private void updateY()
|
|
||||||
// {
|
|
||||||
// baseYPosition = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPosition.Value;
|
|
||||||
// Position = new Vector2(0, baseYPosition);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,205 +2,79 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Screens;
|
|
||||||
using osu.Game.Screens.LAsEzExtensions;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
public partial class EzHoldNoteHead : CompositeDrawable
|
public partial class EzHoldNoteHead : EzNoteBase
|
||||||
{
|
{
|
||||||
private Bindable<bool> enabledColor = null!;
|
protected override bool ShowSeparators => true;
|
||||||
private Bindable<double> nonSquareNoteHeight = null!;
|
|
||||||
|
|
||||||
private TextureAnimation animation = null!;
|
|
||||||
private Container container = null!;
|
|
||||||
private EzNoteSideLine? noteSeparatorsL;
|
|
||||||
private EzNoteSideLine? noteSeparatorsR;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Column column { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private StageDefinition stageDefinition { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzLocalTextureFactory factory { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
FillMode = FillMode.Fill;
|
FillMode = FillMode.Fill;
|
||||||
|
|
||||||
nonSquareNoteHeight = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
|
||||||
enabledColor = ezSkinConfig.GetBindable<bool>(EzSkinSetting.ColorSettingsEnabled);
|
|
||||||
OnSkinChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void OnDrawableChanged()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
string newComponentName = $"{ColorPrefix}longnote/head";
|
||||||
|
|
||||||
nonSquareNoteHeight.BindValueChanged(_ => updateSizes(), true);
|
var animation = Factory.CreateAnimation(newComponentName);
|
||||||
ezSkinConfig.OnSettingsChanged += OnConfigChanged;
|
|
||||||
factory.OnNoteChanged += OnSkinChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
if (animation is TextureAnimation textureAnimation && textureAnimation.FrameCount == 0)
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (isDisposing)
|
|
||||||
{
|
|
||||||
ezSkinConfig.OnSettingsChanged -= OnConfigChanged;
|
|
||||||
factory.OnNoteChanged -= OnSkinChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Color4 NoteColor
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
int keyMode = stageDefinition.Columns;
|
|
||||||
int columnIndex = column.Index;
|
|
||||||
return ezSkinConfig.GetColumnColor(keyMode, columnIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual string ColorPrefix
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (enabledColor.Value)
|
|
||||||
return "white";
|
|
||||||
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1")
|
|
||||||
return "green";
|
|
||||||
|
|
||||||
int logicalIndex = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < column.Index; i++)
|
|
||||||
{
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, i) != "S1")
|
|
||||||
logicalIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return logicalIndex % 2 == 0 ? "white" : "blue";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual string ComponentName => $"{ColorPrefix}longnote/head";
|
|
||||||
|
|
||||||
private void loadAnimation()
|
|
||||||
{
|
|
||||||
ClearInternal();
|
|
||||||
animation = factory.CreateAnimation(ComponentName);
|
|
||||||
container = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Masking = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
noteSeparatorsL = new EzNoteSideLine
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
};
|
|
||||||
noteSeparatorsR = new EzNoteSideLine
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
// Rotation = 180,
|
|
||||||
};
|
|
||||||
AddInternal(new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Stretch,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
noteSeparatorsL,
|
|
||||||
noteSeparatorsR
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (animation.FrameCount == 0)
|
|
||||||
{
|
{
|
||||||
animation.Dispose();
|
animation.Dispose();
|
||||||
animation = factory.CreateAnimation($"{ColorPrefix}note");
|
animation = Factory.CreateAnimation($"{ColorPrefix}note");
|
||||||
var x = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Child = animation,
|
|
||||||
};
|
|
||||||
if (x.Child is TextureAnimation)
|
|
||||||
container.Child = x;
|
|
||||||
|
|
||||||
OnConfigChanged();
|
if (animation is TextureAnimation newTexture && newTexture.FrameCount == 0)
|
||||||
AddInternal(container);
|
{
|
||||||
|
animation.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainContainer != null)
|
||||||
|
{
|
||||||
|
MainContainer.Clear();
|
||||||
|
MainContainer.RelativeSizeAxes = Axes.X;
|
||||||
|
MainContainer.Anchor = Anchor.BottomCentre;
|
||||||
|
MainContainer.Origin = Anchor.BottomCentre;
|
||||||
|
MainContainer.Masking = true;
|
||||||
|
MainContainer.Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Child = animation,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
OnConfigChanged();
|
if (MainContainer != null)
|
||||||
AddInternal(animation);
|
{
|
||||||
|
MainContainer.Clear();
|
||||||
|
MainContainer.Child = animation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
OnConfigChanged();
|
Schedule(UpdateSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSizes()
|
protected override void UpdateSize()
|
||||||
{
|
{
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
base.UpdateSize();
|
||||||
float noteHeight = isSquare
|
float v = NoteSize.Value.Y;
|
||||||
? DrawWidth
|
Height = v;
|
||||||
: (float)(ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight).Value);
|
|
||||||
|
|
||||||
Height = noteHeight;
|
if (MainContainer?.Children.Count > 0 && MainContainer.Child is Container c)
|
||||||
|
|
||||||
if (container.Children.Count > 0 && container.Child is Container c)
|
|
||||||
{
|
{
|
||||||
container.Height = noteHeight / 2;
|
MainContainer.Height = v / 2;
|
||||||
c.Height = noteHeight;
|
c.Height = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConfigChanged()
|
|
||||||
{
|
|
||||||
var noteColor = Color4.White;
|
|
||||||
if (enabledColor.Value)
|
|
||||||
noteColor = NoteColor;
|
|
||||||
|
|
||||||
animation.Colour = noteColor;
|
|
||||||
container.Colour = noteColor;
|
|
||||||
noteSeparatorsL?.UpdateGlowEffect(noteColor);
|
|
||||||
noteSeparatorsR?.UpdateGlowEffect(noteColor);
|
|
||||||
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
updateSizes();
|
|
||||||
Invalidate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSkinChanged() => loadAnimation();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,39 +6,20 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.LAsEzExtensions;
|
using osu.Game.Screens.LAsEzExtensions;
|
||||||
using osu.Game.Skinning;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
public partial class EzHoldNoteHittingLayer : CompositeDrawable
|
public partial class EzHoldNoteHittingLayer : EzNoteBase
|
||||||
{
|
{
|
||||||
|
protected override bool BoolUpdateColor => false;
|
||||||
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||||
private TextureAnimation? animation;
|
private TextureAnimation? animation;
|
||||||
|
|
||||||
private IBindable<double> noteHeightBindable = new Bindable<double>();
|
|
||||||
private IBindable<double> columnWidthBindable = new Bindable<double>();
|
|
||||||
private IBindable<double> specialFactorBindable = new Bindable<double>();
|
|
||||||
|
|
||||||
public IBindable<double> HitPosition { get; set; } = new Bindable<double>();
|
public IBindable<double> HitPosition { get; set; } = new Bindable<double>();
|
||||||
|
|
||||||
// private IBindable<double> hitPosition = new Bindable<double>();
|
|
||||||
// private float baseYPosition;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Column column { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private StageDefinition stageDefinition { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EzLocalTextureFactory factory { get; set; } = null!;
|
private EzLocalTextureFactory factory { get; set; } = null!;
|
||||||
|
|
||||||
@@ -46,31 +27,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
// AutoSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Blending = BlendingParameters.Additive;
|
Blending = BlendingParameters.Additive;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
noteHeightBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
HitPosition = EZSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
||||||
columnWidthBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.ColumnWidth);
|
HitPosition.BindValueChanged(_ => UpdateSize(), true);
|
||||||
specialFactorBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.SpecialFactor);
|
|
||||||
HitPosition = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
|
||||||
noteHeightBindable.BindValueChanged(_ => UpdateLNsLight(), true);
|
|
||||||
columnWidthBindable.BindValueChanged(_ => UpdateLNsLight(), true);
|
|
||||||
specialFactorBindable.BindValueChanged(_ => UpdateLNsLight(), true);
|
|
||||||
// hitPosition.BindValueChanged(_ => updateY(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
onSkinChanged();
|
|
||||||
factory.OnNoteChanged += onSkinChanged;
|
|
||||||
HitPosition.BindValueChanged(pos => Y =
|
|
||||||
LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)pos.NewValue, true);
|
|
||||||
|
|
||||||
IsHitting.BindValueChanged(hitting =>
|
IsHitting.BindValueChanged(hitting =>
|
||||||
{
|
{
|
||||||
ClearTransforms();
|
ClearTransforms();
|
||||||
@@ -89,62 +59,48 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (isDisposing)
|
|
||||||
{
|
|
||||||
factory.OnNoteChanged -= onSkinChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Recycle()
|
public void Recycle()
|
||||||
{
|
{
|
||||||
ClearTransforms();
|
ClearTransforms();
|
||||||
Alpha = 0;
|
Alpha = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAnimation()
|
protected override void OnDrawableChanged()
|
||||||
{
|
{
|
||||||
ClearInternal();
|
string[] componentsToTry = { "longnoteflare", "noteflaregood", "noteflare" };
|
||||||
animation = factory.CreateAnimation("longnoteflare");
|
|
||||||
|
|
||||||
if (animation.FrameCount == 0)
|
foreach (string component in componentsToTry)
|
||||||
{
|
{
|
||||||
animation.Dispose();
|
animation = factory.CreateAnimation(component);
|
||||||
animation = factory.CreateAnimation("noteflaregood");
|
|
||||||
|
|
||||||
if (animation.FrameCount == 0)
|
if (animation.FrameCount > 0)
|
||||||
{
|
{
|
||||||
animation.Dispose();
|
animation.Loop = true;
|
||||||
animation = factory.CreateAnimation("noteflare");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animation.Loop = true;
|
if (animation is TextureAnimation textureAnimation && textureAnimation.FrameCount == 0)
|
||||||
AddInternal(animation);
|
{
|
||||||
UpdateLNsLight();
|
animation.Dispose();
|
||||||
|
UpdateColor();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MainContainer != null)
|
||||||
|
{
|
||||||
|
MainContainer.Clear();
|
||||||
|
if (animation != null) MainContainer.Child = animation;
|
||||||
|
}
|
||||||
|
|
||||||
|
Schedule(UpdateSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateLNsLight()
|
protected override void UpdateSize()
|
||||||
{
|
{
|
||||||
bool isSpecialColumn = ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1";
|
base.UpdateSize();
|
||||||
double columnWidth = columnWidthBindable.Value * (isSpecialColumn ? specialFactorBindable.Value : 1);
|
float v = NoteSize.Value.Y / 2;
|
||||||
|
Position = new Vector2(0, -(float)HitPosition.Value - v);
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
float aspectRatio = factory.GetRatio("whitenote");
|
|
||||||
|
|
||||||
float moveY = isSquare
|
|
||||||
? (float)columnWidth / 2 * aspectRatio
|
|
||||||
: (float)noteHeightBindable.Value * aspectRatio;
|
|
||||||
|
|
||||||
Position = new Vector2(0, moveY);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onSkinChanged()
|
|
||||||
{
|
|
||||||
loadAnimation();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,52 +5,31 @@ using System;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Default;
|
using osu.Game.Rulesets.Mania.Skinning.Default;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osu.Game.Screens.LAsEzExtensions;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
public partial class EzHoldNoteMiddle : CompositeDrawable, IHoldNoteBody
|
public partial class EzHoldNoteMiddle : EzNoteBase, IHoldNoteBody
|
||||||
{
|
{
|
||||||
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
private readonly IBindable<bool> isHitting = new Bindable<bool>();
|
||||||
|
|
||||||
private DrawableHoldNote holdNote = null!;
|
private Container? topContainer;
|
||||||
|
private Container? bodyContainer;
|
||||||
|
private Container? bodyScaleContainer;
|
||||||
|
private Container? bodyInnerContainer;
|
||||||
|
|
||||||
private TextureAnimation? middleAnimation;
|
|
||||||
private TextureAnimation? tailAnimation;
|
|
||||||
private Container topContainer = null!;
|
|
||||||
private Container middleContainer = null!;
|
|
||||||
private Container middleScaleContainer = null!;
|
|
||||||
private Container middleInnerContainer = null!;
|
|
||||||
|
|
||||||
private IBindable<double> noteHeight = new Bindable<double>();
|
|
||||||
private IBindable<double> hitPosition = new Bindable<double>();
|
private IBindable<double> hitPosition = new Bindable<double>();
|
||||||
private Bindable<bool> enabledColor = null!;
|
private EzHoldNoteHittingLayer? hittingLayer;
|
||||||
private Drawable? container;
|
|
||||||
private Drawable? lightContainer;
|
|
||||||
private EzHoldNoteHittingLayer hittingLayer = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Column column { get; set; } = null!;
|
private Column column { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private StageDefinition stageDefinition { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzLocalTextureFactory factory { get; set; } = null!;
|
|
||||||
|
|
||||||
public EzHoldNoteMiddle()
|
public EzHoldNoteMiddle()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@@ -64,148 +43,77 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(DrawableHitObject drawableObject)
|
private void load(DrawableHitObject drawableObject)
|
||||||
{
|
{
|
||||||
holdNote = (DrawableHoldNote)drawableObject;
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
isHitting.BindTo(holdNote.IsHolding);
|
isHitting.BindTo(holdNote.IsHolding);
|
||||||
|
|
||||||
noteHeight = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
hitPosition = EZSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
||||||
hitPosition = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
|
||||||
enabledColor = ezSkinConfig.GetBindable<bool>(EzSkinSetting.ColorSettingsEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Recycle()
|
|
||||||
{
|
|
||||||
ClearTransforms();
|
|
||||||
hittingLayer.Recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
updateSizes();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
OnSkinChanged();
|
|
||||||
noteHeight.BindValueChanged(_ => OnSettingsChanged(), true);
|
|
||||||
factory.OnNoteChanged += OnSkinChanged;
|
|
||||||
ezSkinConfig.OnSettingsChanged += OnSettingsChanged;
|
|
||||||
isHitting.BindValueChanged(onIsHittingChanged, true);
|
isHitting.BindValueChanged(onIsHittingChanged, true);
|
||||||
|
OnSkinChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
// protected override void Update()
|
||||||
{
|
// {
|
||||||
base.Dispose(isDisposing);
|
// base.Update();
|
||||||
|
// }
|
||||||
if (isDisposing)
|
|
||||||
{
|
|
||||||
factory.OnNoteChanged -= OnSkinChanged;
|
|
||||||
ezSkinConfig.OnSettingsChanged -= OnSettingsChanged;
|
|
||||||
lightContainer?.Expire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSkinChanged()
|
private void OnSkinChanged()
|
||||||
{
|
{
|
||||||
if (lightContainer != null)
|
if (hittingLayer != null)
|
||||||
{
|
{
|
||||||
column.TopLevelContainer.Remove(lightContainer, false);
|
column.TopLevelContainer.Remove(hittingLayer, false);
|
||||||
lightContainer.Expire();
|
hittingLayer.Expire();
|
||||||
lightContainer = null;
|
hittingLayer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAnimation();
|
|
||||||
hittingLayer = new EzHoldNoteHittingLayer
|
hittingLayer = new EzHoldNoteHittingLayer
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
IsHitting = { BindTarget = isHitting }
|
IsHitting = { BindTarget = isHitting }
|
||||||
};
|
};
|
||||||
lightContainer = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
Alpha = 0,
|
|
||||||
Child = hittingLayer
|
|
||||||
};
|
|
||||||
|
|
||||||
hittingLayer.HitPosition.BindTo(hitPosition);
|
hittingLayer.HitPosition.BindTo(hitPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
private void onIsHittingChanged(ValueChangedEvent<bool> isHitting)
|
||||||
{
|
{
|
||||||
hittingLayer.IsHitting.Value = isHitting.NewValue;
|
if (hittingLayer != null) hittingLayer.IsHitting.Value = isHitting.NewValue;
|
||||||
|
|
||||||
if (lightContainer == null)
|
if (hittingLayer == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isHitting.NewValue)
|
if (isHitting.NewValue)
|
||||||
{
|
{
|
||||||
lightContainer.ClearTransforms();
|
hittingLayer.ClearTransforms();
|
||||||
|
|
||||||
if (lightContainer.Parent == null)
|
if (hittingLayer.Parent == null)
|
||||||
column.TopLevelContainer.Add(lightContainer);
|
column.TopLevelContainer.Add(hittingLayer);
|
||||||
|
|
||||||
lightContainer.FadeIn(80);
|
hittingLayer.FadeIn(80);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lightContainer.FadeOut(120)
|
hittingLayer.FadeOut(120)
|
||||||
.OnComplete(d => column.TopLevelContainer.Remove(d, false));
|
.OnComplete(d => column.TopLevelContainer.Remove(d, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual Color4 NoteColor
|
public void Recycle()
|
||||||
{
|
{
|
||||||
get
|
ClearTransforms();
|
||||||
{
|
hittingLayer?.Recycle();
|
||||||
int keyMode = stageDefinition.Columns;
|
|
||||||
int columnIndex = column.Index;
|
|
||||||
return ezSkinConfig.GetColumnColor(keyMode, columnIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string ColorPrefix
|
protected override void OnDrawableChanged()
|
||||||
{
|
{
|
||||||
get
|
string newComponentName = $"{ColorPrefix}note";
|
||||||
{
|
var body = Factory.CreateAnimation($"{ColorPrefix}longnote/middle");
|
||||||
if (enabledColor.Value)
|
var tail = Factory.CreateAnimation($"{ColorPrefix}longnote/tail");
|
||||||
return "white";
|
|
||||||
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1")
|
if (body.FrameCount == 0)
|
||||||
return "green";
|
body = Factory.CreateAnimation(newComponentName);
|
||||||
|
|
||||||
int logicalIndex = 0;
|
if (tail.FrameCount == 0)
|
||||||
|
tail = Factory.CreateAnimation(newComponentName);
|
||||||
for (int i = 0; i < column.Index; i++)
|
|
||||||
{
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) != "S1")
|
|
||||||
logicalIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return logicalIndex % 2 == 0 ? "white" : "blue";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadAnimation()
|
|
||||||
{
|
|
||||||
ClearInternal();
|
|
||||||
string backupComponentName = $"{ColorPrefix}note";
|
|
||||||
middleAnimation = factory.CreateAnimation($"{ColorPrefix}longnote/middle");
|
|
||||||
tailAnimation = factory.CreateAnimation($"{ColorPrefix}longnote/tail");
|
|
||||||
|
|
||||||
if (middleAnimation.FrameCount == 0)
|
|
||||||
{
|
|
||||||
middleAnimation.Dispose();
|
|
||||||
middleAnimation = factory.CreateAnimation(backupComponentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tailAnimation.FrameCount == 0)
|
|
||||||
{
|
|
||||||
tailAnimation.Dispose();
|
|
||||||
tailAnimation = factory.CreateAnimation(backupComponentName);
|
|
||||||
}
|
|
||||||
|
|
||||||
topContainer = new Container
|
topContainer = new Container
|
||||||
{
|
{
|
||||||
@@ -218,67 +126,80 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Child = tailAnimation
|
Child = tail
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
middleContainer = new Container
|
bodyContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Child = middleScaleContainer = new Container
|
Child = bodyScaleContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = 1,
|
Height = 1,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Child = middleInnerContainer = new Container
|
Child = bodyInnerContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Child = middleAnimation
|
Child = body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
container = new Container
|
|
||||||
|
if (MainContainer != null)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
MainContainer.Clear();
|
||||||
Children = new[] { middleContainer, topContainer }
|
MainContainer.Children = [bodyContainer, topContainer];
|
||||||
};
|
|
||||||
|
|
||||||
OnSettingsChanged();
|
|
||||||
AddInternal(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSizes()
|
|
||||||
{
|
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
float noteSize = isSquare
|
|
||||||
? DrawWidth
|
|
||||||
: (float)(ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight).Value);
|
|
||||||
|
|
||||||
topContainer.Height = noteSize / 2;
|
|
||||||
if (topContainer.Child is Container topInner)
|
|
||||||
topInner.Height = noteSize;
|
|
||||||
|
|
||||||
float middleHeight = Math.Max(DrawHeight - noteSize / 2, noteSize / 2);
|
|
||||||
|
|
||||||
middleContainer.Y = noteSize / 2;
|
|
||||||
middleContainer.Height = middleHeight + 2;
|
|
||||||
middleScaleContainer.Scale = new Vector2(1, DrawHeight - noteSize / 2);
|
|
||||||
middleInnerContainer.Height = noteSize;
|
|
||||||
middleInnerContainer.Y = -noteSize / 2;
|
|
||||||
Invalidate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSettingsChanged()
|
|
||||||
{
|
|
||||||
if (enabledColor.Value && container != null)
|
|
||||||
{
|
|
||||||
container.Colour = NoteColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Schedule(updateSizes);
|
Schedule(UpdateSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateSize()
|
||||||
|
{
|
||||||
|
base.UpdateSize();
|
||||||
|
if (MainContainer?.Children.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float v = NoteSize.Value.Y;
|
||||||
|
|
||||||
|
if (topContainer?.Child is Container topInner)
|
||||||
|
{
|
||||||
|
topContainer.Height = v / 2;
|
||||||
|
topInner.Height = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
float middleHeight = Math.Max(DrawHeight - v / 2, v / 2);
|
||||||
|
|
||||||
|
if (bodyContainer != null)
|
||||||
|
{
|
||||||
|
bodyContainer.Y = v / 2;
|
||||||
|
bodyContainer.Height = middleHeight + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyScaleContainer != null)
|
||||||
|
bodyScaleContainer.Scale = new Vector2(1, DrawHeight - v / 2);
|
||||||
|
|
||||||
|
if (bodyInnerContainer != null)
|
||||||
|
{
|
||||||
|
bodyInnerContainer.Height = v;
|
||||||
|
bodyInnerContainer.Y = -v / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (isDisposing)
|
||||||
|
{
|
||||||
|
hittingLayer?.Expire();
|
||||||
|
topContainer?.Expire();
|
||||||
|
bodyContainer?.Expire();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
public partial class EzHoldNoteTail : CompositeDrawable
|
public partial class EzHoldNoteTail : CompositeDrawable
|
||||||
{
|
{
|
||||||
private EzHoldNoteHittingLayer hittingLayer = null!;
|
private readonly EzHoldNoteHittingLayer hittingLayer = null!;
|
||||||
private TextureAnimation? animation;
|
private TextureAnimation? animation;
|
||||||
private Container container = null!;
|
private Container container = null!;
|
||||||
|
|
||||||
private EzSkinSettingsManager ezSkinConfig = null!;
|
private EzSkinSettingsManager ezSkinConfig = null!;
|
||||||
private Bindable<bool> enabledColor = null!;
|
private Bindable<bool> enabledColor = null!;
|
||||||
private float noteSize;
|
|
||||||
private bool isSquare;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Column column { get; set; } = null!;
|
private Column column { get; set; } = null!;
|
||||||
@@ -48,9 +46,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Alpha = 0f;
|
Alpha = 0f;
|
||||||
|
|
||||||
hittingLayer = new EzHoldNoteHittingLayer { RelativeSizeAxes = Axes.Both };
|
|
||||||
AddInternal(hittingLayer);
|
|
||||||
|
|
||||||
if (drawableObject != null)
|
if (drawableObject != null)
|
||||||
{
|
{
|
||||||
// accentColour.BindTo(drawableObject.AccentColour);
|
// accentColour.BindTo(drawableObject.AccentColour);
|
||||||
@@ -64,13 +59,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
|
|
||||||
private void updateSizes()
|
private void updateSizes()
|
||||||
{
|
{
|
||||||
isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
noteSize = isSquare
|
|
||||||
? DrawWidth
|
|
||||||
: (float)(ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight).Value);
|
|
||||||
|
|
||||||
container.Height = noteSize / 2;
|
|
||||||
if (animation != null) animation.Height = noteSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@@ -78,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
loadAnimation();
|
loadAnimation();
|
||||||
factory.OnNoteChanged += onSkinChanged;
|
factory.OnNoteChanged += onSkinChanged;
|
||||||
ezSkinConfig.OnSettingsChanged += onSettingsChanged;
|
factory.OnNoteSizeChanged += onNoteSizeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSkinChanged()
|
private void onSkinChanged()
|
||||||
@@ -86,7 +74,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
Schedule(loadAnimation);
|
Schedule(loadAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onSettingsChanged()
|
private void onNoteSizeChanged()
|
||||||
{
|
{
|
||||||
if (enabledColor.Value)
|
if (enabledColor.Value)
|
||||||
container.Colour = NoteColor;
|
container.Colour = NoteColor;
|
||||||
@@ -145,7 +133,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onSettingsChanged();
|
onNoteSizeChanged();
|
||||||
AddInternal(container);
|
AddInternal(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
OnSkinChanged();
|
OnSkinChanged();
|
||||||
factory.OnStageChanged += OnSkinChanged;
|
factory.OnStageChanged += OnSkinChanged;
|
||||||
ezSkinConfig.OnSettingsChanged += OnConfigChanged;
|
factory.OnNoteSizeChanged += OnConfigChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
if (isDisposing)
|
if (isDisposing)
|
||||||
{
|
{
|
||||||
factory.OnStageChanged -= OnSkinChanged;
|
factory.OnStageChanged -= OnSkinChanged;
|
||||||
ezSkinConfig.OnSettingsChanged -= OnConfigChanged;
|
factory.OnNoteSizeChanged -= OnConfigChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,14 +67,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1")
|
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S")
|
||||||
return "02";
|
return "02";
|
||||||
|
|
||||||
int logicalIndex = 0;
|
int logicalIndex = 0;
|
||||||
|
|
||||||
for (int i = 0; i < column.Index; i++)
|
for (int i = 0; i < column.Index; i++)
|
||||||
{
|
{
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, i) == "S1")
|
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, i) == "S")
|
||||||
logicalIndex++;
|
logicalIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,21 +107,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
AddInternal(container);
|
AddInternal(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSizes()
|
|
||||||
{
|
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
Height = isSquare
|
|
||||||
? DrawWidth
|
|
||||||
: (float)(ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight).Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConfigChanged()
|
private void OnConfigChanged()
|
||||||
{
|
{
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
updateSizes();
|
|
||||||
Invalidate();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSkinChanged() => loadAnimation();
|
private void OnSkinChanged() => loadAnimation();
|
||||||
|
|||||||
@@ -2,181 +2,48 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
|
||||||
using osu.Game.Screens;
|
|
||||||
using osu.Game.Screens.LAsEzExtensions;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
public partial class EzNote : CompositeDrawable
|
public partial class EzNote : EzNoteBase
|
||||||
{
|
{
|
||||||
private Bindable<bool> enabledColor = null!;
|
protected override bool ShowSeparators => true;
|
||||||
private Bindable<double> nonSquareNoteHeight = null!;
|
|
||||||
|
|
||||||
private TextureAnimation animation = null!;
|
|
||||||
private Drawable container = null!;
|
|
||||||
private EzNoteSideLine? noteSeparatorsL;
|
|
||||||
private EzNoteSideLine? noteSeparatorsR;
|
|
||||||
|
|
||||||
protected virtual bool ShowSeparators => true;
|
|
||||||
protected virtual bool UseColorization => true;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private Column column { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private StageDefinition stageDefinition { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzLocalTextureFactory factory { get; set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
FillMode = FillMode.Fill;
|
FillMode = FillMode.Fill;
|
||||||
|
|
||||||
enabledColor = ezSkinConfig.GetBindable<bool>(EzSkinSetting.ColorSettingsEnabled);
|
|
||||||
nonSquareNoteHeight = ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void OnDrawableChanged()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
string newComponentName = $"{ColorPrefix}note";
|
||||||
OnSkinChanged();
|
|
||||||
enabledColor.BindValueChanged(_ => OnConfigChanged(), true);
|
|
||||||
nonSquareNoteHeight.BindValueChanged(_ => updateSizes(), true);
|
|
||||||
ezSkinConfig.OnSettingsChanged += OnConfigChanged;
|
|
||||||
factory.OnNoteChanged += OnSkinChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
var animation = Factory.CreateAnimation(newComponentName);
|
||||||
{
|
|
||||||
base.Dispose(isDisposing);
|
|
||||||
|
|
||||||
if (isDisposing)
|
if (animation is TextureAnimation textureAnimation && textureAnimation.FrameCount == 0)
|
||||||
{
|
{
|
||||||
ezSkinConfig.OnSettingsChanged -= OnConfigChanged;
|
animation.Dispose();
|
||||||
factory.OnNoteChanged -= OnSkinChanged;
|
UpdateColor();
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual Color4 NoteColor
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
int keyMode = stageDefinition.Columns;
|
|
||||||
int columnIndex = column.Index;
|
|
||||||
return ezSkinConfig.GetColumnColor(keyMode, columnIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual string ColorPrefix
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (enabledColor.Value)
|
|
||||||
return "white";
|
|
||||||
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, column.Index) == "S1")
|
|
||||||
return "green";
|
|
||||||
|
|
||||||
int logicalIndex = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < column.Index; i++)
|
|
||||||
{
|
|
||||||
if (ezSkinConfig.GetColumnType(stageDefinition.Columns, i) != "S1")
|
|
||||||
logicalIndex++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return logicalIndex % 2 == 0 ? "white" : "blue";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual string ComponentName => $"{ColorPrefix}note";
|
|
||||||
|
|
||||||
private void loadAnimation()
|
|
||||||
{
|
|
||||||
ClearInternal();
|
|
||||||
animation = factory.CreateAnimation(ComponentName);
|
|
||||||
container = new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Child = animation
|
|
||||||
};
|
|
||||||
|
|
||||||
if (ShowSeparators)
|
|
||||||
{
|
|
||||||
noteSeparatorsL = new EzNoteSideLine
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
};
|
|
||||||
noteSeparatorsR = new EzNoteSideLine
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Fill,
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
// Rotation = 180,
|
|
||||||
};
|
|
||||||
AddInternal(new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
FillMode = FillMode.Stretch,
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
noteSeparatorsL,
|
|
||||||
noteSeparatorsR
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OnConfigChanged();
|
if (MainContainer != null)
|
||||||
AddInternal(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateSizes()
|
|
||||||
{
|
|
||||||
bool isSquare = factory.IsSquareNote("whitenote");
|
|
||||||
Height = isSquare
|
|
||||||
? DrawWidth
|
|
||||||
: (float)(ezSkinConfig.GetBindable<double>(EzSkinSetting.NonSquareNoteHeight).Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnConfigChanged()
|
|
||||||
{
|
|
||||||
var noteColor = Color4.White;
|
|
||||||
if (enabledColor.Value && UseColorization)
|
|
||||||
noteColor = NoteColor;
|
|
||||||
|
|
||||||
container.Colour = noteColor;
|
|
||||||
|
|
||||||
noteSeparatorsL?.UpdateGlowEffect(noteColor);
|
|
||||||
noteSeparatorsR?.UpdateGlowEffect(noteColor);
|
|
||||||
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
{
|
||||||
updateSizes();
|
MainContainer.Clear();
|
||||||
Invalidate();
|
MainContainer.Child = animation;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
Schedule(UpdateSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSkinChanged() => loadAnimation();
|
protected override void UpdateSize()
|
||||||
|
{
|
||||||
|
base.UpdateSize();
|
||||||
|
Height = NoteSize.Value.Y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
171
osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNoteBase.cs
Normal file
171
osu.Game.Rulesets.Mania/Skinning/EzStylePro/EzNoteBase.cs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Screens;
|
||||||
|
using osu.Game.Screens.LAsEzExtensions;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
|
{
|
||||||
|
public abstract partial class EzNoteBase : CompositeDrawable
|
||||||
|
{
|
||||||
|
protected virtual bool BoolUpdateColor => true;
|
||||||
|
protected virtual bool UseColorization => true;
|
||||||
|
protected virtual bool ShowSeparators => false;
|
||||||
|
protected int KeyMode;
|
||||||
|
protected int ColumnIndex;
|
||||||
|
|
||||||
|
protected Container? SeparatorsContainer { get; private set; }
|
||||||
|
protected Container? MainContainer { get; private set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected Column Column { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected StageDefinition StageDefinition { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EzSkinSettingsManager EZSkinConfig { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EzLocalTextureFactory Factory { get; private set; } = null!;
|
||||||
|
|
||||||
|
private IBindable<Colour4> columnColorBindable = null!;
|
||||||
|
protected Bindable<bool> EnabledColor = null!;
|
||||||
|
protected Bindable<Vector2> NoteSize = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
KeyMode = StageDefinition.Columns;
|
||||||
|
ColumnIndex = Column.Index;
|
||||||
|
EnabledColor = EZSkinConfig.GetBindable<bool>(EzSkinSetting.ColorSettingsEnabled);
|
||||||
|
columnColorBindable = EZSkinConfig.GetColumnColorBindable(KeyMode, ColumnIndex);
|
||||||
|
NoteSize = Factory.GetNoteSize(KeyMode, ColumnIndex);
|
||||||
|
|
||||||
|
createSeparators();
|
||||||
|
MainContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
AddInternal(MainContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSeparators()
|
||||||
|
{
|
||||||
|
var noteSeparatorsL = new EzNoteSideLine
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
|
||||||
|
var noteSeparatorsR = new EzNoteSideLine
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
FillMode = FillMode.Fill,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
};
|
||||||
|
|
||||||
|
SeparatorsContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
FillMode = FillMode.Stretch,
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Alpha = ShowSeparators ? 1f : 0f,
|
||||||
|
Children = [noteSeparatorsL, noteSeparatorsR]
|
||||||
|
};
|
||||||
|
|
||||||
|
AddInternal(SeparatorsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
EnabledColor.BindValueChanged(_ => UpdateColor(), true);
|
||||||
|
columnColorBindable.BindValueChanged(_ => UpdateColor(), true);
|
||||||
|
NoteSize.BindValueChanged(_ => UpdateSize(), true);
|
||||||
|
|
||||||
|
Factory.OnNoteChanged += OnDrawableChanged;
|
||||||
|
// Factory.OnNoteSizeChanged += UpdateSize;
|
||||||
|
Scheduler.AddOnce(OnDrawableChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
Factory.OnNoteChanged -= OnDrawableChanged;
|
||||||
|
// Factory.OnNoteSizeChanged -= UpdateSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Colour4 NoteColor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (EnabledColor.Value && UseColorization)
|
||||||
|
return EZSkinConfig.GetColumnColor(KeyMode, ColumnIndex);
|
||||||
|
|
||||||
|
return Colour4.White;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual string ColorPrefix
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (EnabledColor.Value) return "white";
|
||||||
|
|
||||||
|
string keyType = EZSkinConfig.GetColumnType(KeyMode, ColumnIndex);
|
||||||
|
|
||||||
|
return keyType switch
|
||||||
|
{
|
||||||
|
"A" => "white",
|
||||||
|
"B" => "blue",
|
||||||
|
"S" => "green",
|
||||||
|
"E" => "white",
|
||||||
|
"P" => "green",
|
||||||
|
_ => "white"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void UpdateColor()
|
||||||
|
{
|
||||||
|
if (BoolUpdateColor)
|
||||||
|
{
|
||||||
|
if (MainContainer != null)
|
||||||
|
MainContainer.Colour = NoteColor;
|
||||||
|
|
||||||
|
if (SeparatorsContainer?.Children != null)
|
||||||
|
{
|
||||||
|
foreach (var child in SeparatorsContainer.Children)
|
||||||
|
{
|
||||||
|
if (child is EzNoteSideLine sideLine)
|
||||||
|
sideLine.UpdateGlowEffect(NoteColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void UpdateSize()
|
||||||
|
{
|
||||||
|
NoteSize = Factory.GetNoteSize(KeyMode, ColumnIndex);
|
||||||
|
UpdateColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnDrawableChanged() { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@@ -11,7 +10,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Screens;
|
using osu.Game.Screens;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
||||||
{
|
{
|
||||||
@@ -74,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
separator.Height = (float)noteTrackLineHeight.Value;
|
separator.Height = (float)noteTrackLineHeight.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateGlowEffect(Color4 color)
|
public void UpdateGlowEffect(Colour4 color)
|
||||||
{
|
{
|
||||||
separator.Colour = new ColourInfo
|
separator.Colour = new ColourInfo
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
|
|
||||||
hitPositon = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
hitPositon = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
||||||
columnWidth = ezSkinConfig.GetBindable<double>(EzSkinSetting.ColumnWidth);
|
columnWidth = ezSkinConfig.GetBindable<double>(EzSkinSetting.ColumnWidth);
|
||||||
hitPositon.BindValueChanged(_ => OnConfigChanged());
|
hitPositon.BindValueChanged(_ => updateSizes());
|
||||||
columnWidth.BindValueChanged(_ => OnConfigChanged());
|
columnWidth.BindValueChanged(_ => updateSizes());
|
||||||
OnSkinChanged();
|
OnSkinChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
// updateSizes();
|
// updateSizes();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private void loadAnimation()
|
private void OnSkinChanged()
|
||||||
{
|
{
|
||||||
ClearInternal();
|
ClearInternal();
|
||||||
|
|
||||||
@@ -77,8 +77,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
Child = stageBottom
|
Child = stageBottom
|
||||||
};
|
};
|
||||||
// sprite.Depth = float.MinValue;
|
// sprite.Depth = float.MinValue;
|
||||||
OnConfigChanged();
|
|
||||||
AddInternal(sprite);
|
AddInternal(sprite);
|
||||||
|
Schedule(updateSizes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSizes()
|
private void updateSizes()
|
||||||
@@ -93,16 +93,5 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
sprite.Y = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPositon.Value - DrawHeight * 0.865f;
|
sprite.Y = LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION - (float)hitPositon.Value - DrawHeight * 0.865f;
|
||||||
// Position = new Vector2(0, 415 + 110 - (float)hitPositon.Value);
|
// Position = new Vector2(0, 415 + 110 - (float)hitPositon.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConfigChanged()
|
|
||||||
{
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
|
||||||
updateSizes();
|
|
||||||
Invalidate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSkinChanged() => loadAnimation();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
|
|
||||||
case ManiaSkinComponents.HitExplosion:
|
case ManiaSkinComponents.HitExplosion:
|
||||||
return new EzHitExplosion();
|
return new EzHitExplosion();
|
||||||
|
// return HitExplosionPool.Rent();
|
||||||
|
|
||||||
case ManiaSkinComponents.StageBackground:
|
case ManiaSkinComponents.StageBackground:
|
||||||
return new Ez2StageBackground();
|
return new Ez2StageBackground();
|
||||||
@@ -226,7 +227,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.EzStylePro
|
|||||||
{
|
{
|
||||||
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||||
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||||
bool isSpecialColumn = ezSkinConfig.GetColumnType(stage.Columns, columnIndex) == "S1";
|
bool isSpecialColumn = ezSkinConfig.GetColumnType(stage.Columns, columnIndex) == "S";
|
||||||
float width = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1f);
|
float width = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1f);
|
||||||
// float hitPositionValue = (float)hitPosition.Value; // + (float)virtualHitPosition.Value - 110f;
|
// float hitPositionValue = (float)hitPosition.Value; // + (float)virtualHitPosition.Value - 110f;
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private Bindable<double> hitPositonBindable = new Bindable<double>();
|
private Bindable<double> hitPositonBindable = new Bindable<double>();
|
||||||
|
private Bindable<bool> globalHitPosition = new Bindable<bool>();
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
@@ -30,17 +32,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, EzSkinSettingsManager ezSkinConfig, IScrollingInfo scrollingInfo)
|
private void load(ISkinSource skin, EzSkinSettingsManager ezSkinConfig, IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
hitPositonBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
|
||||||
hitPositonBindable.BindValueChanged(_ => UpdateHitPosition(), true);
|
|
||||||
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? (float)hitPositonBindable.Value;
|
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
|
|
||||||
|
ezSkinConfig.GetBindable<bool>(EzSkinSetting.GlobalHitPosition).BindValueChanged(_ => updateHitPosition(skin, ezSkinConfig));
|
||||||
|
ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition).BindValueChanged(_ => updateHitPosition(skin, ezSkinConfig));
|
||||||
|
|
||||||
|
updateHitPosition(skin, ezSkinConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void UpdateHitPosition()
|
private void updateHitPosition(ISkinSource skin, EzSkinSettingsManager ezSkinConfig)
|
||||||
{
|
{
|
||||||
hitPosition = (float)hitPositonBindable.Value;
|
bool globalHitPositionValue = ezSkinConfig.GetBindable<bool>(EzSkinSetting.GlobalHitPosition).Value;
|
||||||
|
double hitPositionValue = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition).Value;
|
||||||
|
|
||||||
|
hitPosition = globalHitPositionValue
|
||||||
|
? (float)hitPositionValue
|
||||||
|
: skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? (float)hitPositionValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isSpecialColumn =
|
bool isSpecialColumn =
|
||||||
ezSkinConfig.GetColumnType(stageDefinition.Columns, i) == "S1";
|
ezSkinConfig.GetColumnType(stageDefinition.Columns, i) == "S";
|
||||||
float ezWidth = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1);
|
float ezWidth = (float)columnWidthBindable.Value * (isSpecialColumn ? (float)specialFactorBindable.Value : 1);
|
||||||
|
|
||||||
switch (ezColumnWidthStyle.Value)
|
switch (ezColumnWidthStyle.Value)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
private EzSkinSettingsManager ezSkinConfig { get; set; } = null!;
|
||||||
|
|
||||||
private Bindable<double> hitPositonBindable = new Bindable<double>();
|
private Bindable<double> hitPositonBindable = new Bindable<double>();
|
||||||
|
private readonly Bindable<bool> globalHitPosition = new Bindable<bool>();
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
@@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(scrollingInfo.Direction);
|
||||||
Direction.BindValueChanged(_ => UpdateHitPosition(), true);
|
Direction.BindValueChanged(_ => UpdateHitPosition(), true);
|
||||||
|
|
||||||
|
ezSkinConfig.BindWith(EzSkinSetting.GlobalHitPosition, globalHitPosition);
|
||||||
|
globalHitPosition.BindValueChanged(_ => UpdateHitPosition(), true);
|
||||||
hitPositonBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
hitPositonBindable = ezSkinConfig.GetBindable<double>(EzSkinSetting.HitPosition);
|
||||||
hitPositonBindable.BindValueChanged(_ => UpdateHitPosition(), true);
|
hitPositonBindable.BindValueChanged(_ => UpdateHitPosition(), true);
|
||||||
skin.SourceChanged += onSkinChanged;
|
skin.SourceChanged += onSkinChanged;
|
||||||
@@ -40,9 +43,16 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
|
|
||||||
protected virtual void UpdateHitPosition()
|
protected virtual void UpdateHitPosition()
|
||||||
{
|
{
|
||||||
float hitPosition = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
float hitPosition;
|
||||||
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
|
||||||
?? (float)hitPositonBindable.Value;
|
if (globalHitPosition.Value)
|
||||||
|
hitPosition = (float)hitPositonBindable.Value;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hitPosition = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
|
||||||
|
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value
|
||||||
|
?? (float)hitPositonBindable.Value;
|
||||||
|
}
|
||||||
|
|
||||||
Padding = Direction.Value == ScrollingDirection.Up
|
Padding = Direction.Value == ScrollingDirection.Up
|
||||||
? new MarginPadding { Top = hitPosition }
|
? new MarginPadding { Top = hitPosition }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
var difficulty = new BeatmapDifficulty { ApproachRate = originalApproachRate };
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(originalApproachRate));
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModHalfTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(1.67).Within(0.01));
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(2.22).Within(0.01));
|
||||||
@@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
var ruleset = new OsuRuleset();
|
var ruleset = new OsuRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new OsuModDoubleTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
Assert.That(adjustedDifficulty.ApproachRate, Is.EqualTo(7.67).Within(0.01));
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(7.77).Within(0.01));
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Storyboards;
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@@ -152,6 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
[FlakyTest]
|
||||||
public void TestSpinPerMinuteOnRewind()
|
public void TestSpinPerMinuteOnRewind()
|
||||||
{
|
{
|
||||||
double estimatedSpm = 0;
|
double estimatedSpm = 0;
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
|||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
|
||||||
|
|
||||||
overallDifficulty = (80 - greatHitWindow) / 6;
|
overallDifficulty = (79.5 - greatHitWindow) / 6;
|
||||||
approachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5;
|
approachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5;
|
||||||
|
|
||||||
if (osuAttributes.SliderCount > 0)
|
if (osuAttributes.SliderCount > 0)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@@ -9,5 +10,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
public class OsuModEasy : ModEasyWithExtraLives
|
public class OsuModEasy : ModEasyWithExtraLives
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"Larger circles, more forgiving HP drain, less accuracy required, and extra lives!";
|
public override LocalisableString Description => @"Larger circles, more forgiving HP drain, less accuracy required, and extra lives!";
|
||||||
|
|
||||||
|
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty *= ADJUST_RATIO;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
base.ApplyToDifficulty(difficulty);
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
|
||||||
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
|
difficulty.CircleSize = Math.Min(difficulty.CircleSize * 1.3f, 10.0f); // CS uses a custom 1.3 ratio.
|
||||||
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
|
difficulty.ApproachRate = Math.Min(difficulty.ApproachRate * ADJUST_RATIO, 10.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ using osu.Game.Rulesets.Objects;
|
|||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
@@ -83,7 +83,12 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Judgement CreateJudgement() => new OsuJudgement();
|
public override Judgement CreateJudgement() => new StrictTrackingTailJudgement();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StrictTrackingTailJudgement : SliderTailCircle.TailJudgement
|
||||||
|
{
|
||||||
|
public override HitResult MinResult => HitResult.LargeTickMiss;
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class StrictTrackingDrawableSliderTail : DrawableSliderTail
|
private partial class StrictTrackingDrawableSliderTail : DrawableSliderTail
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ using osu.Game.Scoring;
|
|||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
@@ -365,9 +366,10 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
|
|
||||||
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
/// <seealso cref="OsuHitObject.ApplyDefaultsToSelf"/>
|
||||||
/// <seealso cref="OsuHitWindows"/>
|
/// <seealso cref="OsuHitWindows"/>
|
||||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
|
||||||
{
|
{
|
||||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||||
|
double rate = ModUtils.CalculateRateWithMods(mods);
|
||||||
|
|
||||||
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
|
double preempt = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.ApproachRate, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
|
||||||
preempt /= rate;
|
preempt /= rate;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests
|
namespace osu.Game.Rulesets.Taiko.Tests
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
var ruleset = new TaikoRuleset();
|
var ruleset = new TaikoRuleset();
|
||||||
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
var difficulty = new BeatmapDifficulty { OverallDifficulty = originalOverallDifficulty };
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, []);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(originalOverallDifficulty));
|
||||||
}
|
}
|
||||||
@@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
var ruleset = new TaikoRuleset();
|
var ruleset = new TaikoRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 0.75);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModHalfTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(1.11).Within(0.01));
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
var ruleset = new TaikoRuleset();
|
var ruleset = new TaikoRuleset();
|
||||||
var difficulty = new BeatmapDifficulty();
|
var difficulty = new BeatmapDifficulty();
|
||||||
|
|
||||||
var adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(difficulty, 1.5);
|
var adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(difficulty, [new TaikoModDoubleTime()]);
|
||||||
|
|
||||||
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01));
|
Assert.That(adjustedDifficulty.OverallDifficulty, Is.EqualTo(8.89).Within(0.01));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyToDifficulty(difficulty);
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty *= ADJUST_RATIO;
|
||||||
difficulty.SliderMultiplier *= slider_multiplier;
|
difficulty.SliderMultiplier *= slider_multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
public override void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyToDifficulty(difficulty);
|
base.ApplyToDifficulty(difficulty);
|
||||||
|
|
||||||
|
difficulty.OverallDifficulty = Math.Min(difficulty.OverallDifficulty * ADJUST_RATIO, 10.0f);
|
||||||
difficulty.SliderMultiplier *= slider_multiplier;
|
difficulty.SliderMultiplier *= slider_multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ using osu.Game.Rulesets.Taiko.Configuration;
|
|||||||
using osu.Game.Rulesets.Taiko.Edit.Setup;
|
using osu.Game.Rulesets.Taiko.Edit.Setup;
|
||||||
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
using osu.Game.Rulesets.Taiko.Skinning.Default;
|
||||||
using osu.Game.Screens.Edit.Setup;
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko
|
namespace osu.Game.Rulesets.Taiko
|
||||||
{
|
{
|
||||||
@@ -270,9 +271,10 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <seealso cref="TaikoHitWindows"/>
|
/// <seealso cref="TaikoHitWindows"/>
|
||||||
public override BeatmapDifficulty GetRateAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, double rate)
|
public override BeatmapDifficulty GetAdjustedDisplayDifficulty(IBeatmapDifficultyInfo difficulty, IReadOnlyCollection<Mod> mods)
|
||||||
{
|
{
|
||||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||||
|
double rate = ModUtils.CalculateRateWithMods(mods);
|
||||||
|
|
||||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, TaikoHitWindows.GREAT_WINDOW_RANGE);
|
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, TaikoHitWindows.GREAT_WINDOW_RANGE);
|
||||||
greatHitWindow /= rate;
|
greatHitWindow /= rate;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using ManagedBass;
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@@ -10,7 +11,9 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Checks;
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Audio;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Editing.Checks
|
namespace osu.Game.Tests.Editing.Checks
|
||||||
{
|
{
|
||||||
@@ -28,9 +31,13 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
BeatmapInfo = new BeatmapInfo
|
BeatmapInfo = new BeatmapInfo
|
||||||
{
|
{
|
||||||
Metadata = new BeatmapMetadata { AudioFile = "abc123.jpg" }
|
Metadata = new BeatmapMetadata()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 0 = No output device. This still allows decoding.
|
||||||
|
if (!Bass.Init(0) && Bass.LastError != Errors.Already)
|
||||||
|
throw new AudioException("Could not initialize Bass.");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -54,6 +61,14 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
Assert.That(check.Run(context), Is.Empty);
|
Assert.That(check.Run(context), Is.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAcceptableOgg()
|
||||||
|
{
|
||||||
|
var context = getContext(208, useOgg: true);
|
||||||
|
|
||||||
|
Assert.That(check.Run(context), Is.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestNullBitrate()
|
public void TestNullBitrate()
|
||||||
{
|
{
|
||||||
@@ -87,6 +102,17 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
|
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTooHighBitrateOgg()
|
||||||
|
{
|
||||||
|
var context = getContext(250, useOgg: true);
|
||||||
|
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooHighBitrate);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTooLowBitrate()
|
public void TestTooLowBitrate()
|
||||||
{
|
{
|
||||||
@@ -98,24 +124,41 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate);
|
Assert.That(issues.Single().Template is CheckAudioQuality.IssueTemplateTooLowBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BeatmapVerifierContext getContext(int? audioBitrate)
|
private BeatmapVerifierContext getContext(int? audioBitrate, bool useOgg = false)
|
||||||
{
|
{
|
||||||
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(audioBitrate).Object);
|
// Update the audio filename and beatmapset files based on the format being tested
|
||||||
|
string audioFileName = useOgg ? "abc123.ogg" : "abc123.mp3";
|
||||||
|
string fileExtension = useOgg ? "ogg" : "mp3";
|
||||||
|
|
||||||
|
beatmap.Metadata.AudioFile = audioFileName;
|
||||||
|
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
Files = { CheckTestHelpers.CreateMockFile(fileExtension) }
|
||||||
|
};
|
||||||
|
|
||||||
|
return new BeatmapVerifierContext(beatmap, getMockWorkingBeatmap(audioBitrate, useOgg).Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the mock of the working beatmap with the given audio properties.
|
/// Returns the mock of the working beatmap with the given audio properties.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="audioBitrate">The bitrate of the audio file the beatmap uses.</param>
|
/// <param name="audioBitrate">The bitrate of the audio file the beatmap uses.</param>
|
||||||
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(int? audioBitrate)
|
/// <param name="useOgg">Whether to use an OGG sample instead of MP3.</param>
|
||||||
|
private Mock<IWorkingBeatmap> getMockWorkingBeatmap(int? audioBitrate, bool useOgg = false)
|
||||||
{
|
{
|
||||||
var mockTrack = new Mock<OsuTestScene.ClockBackedTestWorkingBeatmap.TrackVirtualManual>(new FramedClock(), "virtual");
|
var mockTrack = new Mock<OsuTestScene.ClockBackedTestWorkingBeatmap.TrackVirtualManual>(new FramedClock(), "virtual");
|
||||||
mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate);
|
mockTrack.SetupGet(t => t.Bitrate).Returns(audioBitrate);
|
||||||
|
|
||||||
|
// Use real audio samples for format detection
|
||||||
|
string samplePath = useOgg ? "Samples/test-sample.ogg" : "Samples/test-sample-cut.mp3";
|
||||||
|
|
||||||
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
|
var mockWorkingBeatmap = new Mock<IWorkingBeatmap>();
|
||||||
mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap);
|
mockWorkingBeatmap.SetupGet(w => w.Beatmap).Returns(beatmap);
|
||||||
mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object);
|
mockWorkingBeatmap.SetupGet(w => w.Track).Returns(mockTrack.Object);
|
||||||
|
|
||||||
|
// Return a fresh stream each time GetStream is called to avoid disposed stream issues
|
||||||
|
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(() => TestResources.OpenResource(samplePath));
|
||||||
|
|
||||||
return mockWorkingBeatmap;
|
return mockWorkingBeatmap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,14 +136,39 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFixedReleaseStreamWrittenToConfig()
|
||||||
|
{
|
||||||
|
AddStep("add manager", () =>
|
||||||
|
{
|
||||||
|
config = new OsuConfigManager(LocalStorage);
|
||||||
|
config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
CachedDependencies = [(typeof(OsuConfigManager), config)],
|
||||||
|
Child = manager = new TestUpdateManager(ReleaseStream.Tachyon)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("release stream set to tachyon", () => config.Get<ReleaseStream>(OsuSetting.ReleaseStream), () => Is.EqualTo(ReleaseStream.Tachyon));
|
||||||
|
}
|
||||||
|
|
||||||
private partial class TestUpdateManager : UpdateManager
|
private partial class TestUpdateManager : UpdateManager
|
||||||
{
|
{
|
||||||
|
public override ReleaseStream? FixedReleaseStream { get; }
|
||||||
|
|
||||||
public bool IsPending { get; private set; }
|
public bool IsPending { get; private set; }
|
||||||
public int Invocations { get; private set; }
|
public int Invocations { get; private set; }
|
||||||
public int Completions { get; private set; }
|
public int Completions { get; private set; }
|
||||||
|
|
||||||
private TaskCompletionSource<bool>? pendingCheck;
|
private TaskCompletionSource<bool>? pendingCheck;
|
||||||
|
|
||||||
|
public TestUpdateManager(ReleaseStream? fixedReleaseStream = null)
|
||||||
|
{
|
||||||
|
FixedReleaseStream = fixedReleaseStream;
|
||||||
|
}
|
||||||
|
|
||||||
protected override async Task<bool> PerformUpdateCheck(CancellationToken cancellationToken)
|
protected override async Task<bool> PerformUpdateCheck(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
Invocations++;
|
Invocations++;
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Overlays.Toolbar;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Difficulty;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Menus
|
||||||
|
{
|
||||||
|
public partial class TestSceneToolbarRulesetSelector : OsuTestScene
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(RulesetStore rulesets, OsuGameBase game)
|
||||||
|
{
|
||||||
|
TestRuleset.Resources = new TestResourceStore(game.Resources);
|
||||||
|
|
||||||
|
Dependencies.CacheAs<RulesetStore>(new TestRulesetStore(rulesets));
|
||||||
|
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = Toolbar.HEIGHT,
|
||||||
|
Child = new ToolbarRulesetSelector(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRulesetStore : RulesetStore
|
||||||
|
{
|
||||||
|
public TestRulesetStore(RulesetStore store)
|
||||||
|
{
|
||||||
|
AvailableRulesets = store.AvailableRulesets.Append(new TestRuleset().RulesetInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<RulesetInfo> AvailableRulesets { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRuleset : Ruleset
|
||||||
|
{
|
||||||
|
public static IResourceStore<byte[]> Resources { get; set; } = null!;
|
||||||
|
|
||||||
|
public override IEnumerable<Mod> GetModsFor(ModType type) => Enumerable.Empty<Mod>();
|
||||||
|
|
||||||
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||||
|
|
||||||
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||||
|
|
||||||
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||||
|
|
||||||
|
public override IResourceStore<byte[]> CreateResourceStore() => Resources;
|
||||||
|
|
||||||
|
public override string Description => "Test Ruleset";
|
||||||
|
public override string ShortName => "test";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestResourceStore : ResourceStore<byte[]>
|
||||||
|
{
|
||||||
|
public TestResourceStore(IResourceStore<byte[]> store)
|
||||||
|
: base(store)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerable<string> GetFilenames(string name) => base.GetFilenames(name)
|
||||||
|
.Select(s => s.Replace("UI/ruleset-select-test", "Gameplay/failsound"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
private BeatmapManager beatmaps = null!;
|
private BeatmapManager beatmaps = null!;
|
||||||
private BeatmapSetInfo importedSet = null!;
|
private BeatmapSetInfo importedSet = null!;
|
||||||
|
private BeatmapSetInfo importedSet2 = null!;
|
||||||
|
|
||||||
private TestMultiplayerComponents multiplayerComponents = null!;
|
private TestMultiplayerComponents multiplayerComponents = null!;
|
||||||
|
|
||||||
@@ -81,12 +82,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("import beatmap", () =>
|
AddStep("import beatmap", () =>
|
||||||
{
|
{
|
||||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
|
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||||
|
importedSet2 = beatmaps.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet!)!.Value.Detach();
|
||||||
|
|
||||||
Realm.Write(r =>
|
Realm.Write(r =>
|
||||||
{
|
{
|
||||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||||
});
|
});
|
||||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
|
AddStep("load multiplayer", () => LoadScreen(multiplayerComponents = new TestMultiplayerComponents()));
|
||||||
@@ -1095,6 +1099,112 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddAssert("global beatmap matches second playlist item", () => Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(multiplayerClient.ClientRoom!.Playlist[1].BeatmapID));
|
AddAssert("global beatmap matches second playlist item", () => Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(multiplayerClient.ClientRoom!.Playlist[1].BeatmapID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the local user is not able to change their play style if they haven't downloaded the beatmap (beatmap carousel will be empty).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestCanNotEditDifficultyIfNotDownloaded()
|
||||||
|
{
|
||||||
|
IBeatmap roomBeatmap = null!;
|
||||||
|
|
||||||
|
createRoom(() =>
|
||||||
|
{
|
||||||
|
roomBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||||
|
|
||||||
|
return new Room
|
||||||
|
{
|
||||||
|
Name = "Test Room",
|
||||||
|
QueueMode = QueueMode.AllPlayers,
|
||||||
|
Playlist =
|
||||||
|
[
|
||||||
|
new PlaylistItem(CreateAPIBeatmap(roomBeatmap.BeatmapInfo))
|
||||||
|
{
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
|
Freestyle = true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("editing disallowed", () => !this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().UserStyleEditingEnabled);
|
||||||
|
AddStep("import beatmap", () => beatmaps.Import(roomBeatmap.BeatmapInfo.BeatmapSet!));
|
||||||
|
AddAssert("editing allowed", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().UserStyleEditingEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that the user selection screen is not exited when the beatmap is changed to the same set.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestUserStyleSelectionDoesNotExitWhenBeatmapSetNotChanged()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = "Test Room",
|
||||||
|
QueueMode = QueueMode.AllPlayers,
|
||||||
|
Playlist =
|
||||||
|
[
|
||||||
|
new PlaylistItem(importedSet.Beatmaps.First())
|
||||||
|
{
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
|
Freestyle = true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("open user style selection", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().ShowUserStyleSelect());
|
||||||
|
AddUntilStep("style selection screen opened", () => this.ChildrenOfType<MultiplayerMatchFreestyleSelect>().SingleOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
|
||||||
|
AddStep("change beatmap", () =>
|
||||||
|
{
|
||||||
|
var newItem = multiplayerClient.ServerRoom!.Playlist[0].Clone();
|
||||||
|
var newBeatmap = importedSet.Beatmaps.Last();
|
||||||
|
newItem.BeatmapID = newBeatmap.OnlineID;
|
||||||
|
newItem.BeatmapChecksum = newBeatmap.MD5Hash;
|
||||||
|
|
||||||
|
multiplayerClient.EditPlaylistItem(newItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddWaitStep("wait for potential beatmap change", 2);
|
||||||
|
AddAssert("style selection screen still open", () => this.ChildrenOfType<MultiplayerMatchFreestyleSelect>().SingleOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that the user selection screen is exited when the beatmap is changed to another set.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestUserStyleSelectionExitedWhenBeatmapSetChanged()
|
||||||
|
{
|
||||||
|
createRoom(() => new Room
|
||||||
|
{
|
||||||
|
Name = "Test Room",
|
||||||
|
QueueMode = QueueMode.AllPlayers,
|
||||||
|
Playlist =
|
||||||
|
[
|
||||||
|
new PlaylistItem(importedSet.Beatmaps.First())
|
||||||
|
{
|
||||||
|
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||||
|
Freestyle = true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("open user style selection", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().ShowUserStyleSelect());
|
||||||
|
AddUntilStep("style selection screen opened", () => this.ChildrenOfType<MultiplayerMatchFreestyleSelect>().SingleOrDefault()?.IsCurrentScreen() == true);
|
||||||
|
|
||||||
|
AddStep("change beatmap set", () =>
|
||||||
|
{
|
||||||
|
var newItem = multiplayerClient.ServerRoom!.Playlist[0].Clone();
|
||||||
|
var newBeatmap = importedSet2.Beatmaps.Last();
|
||||||
|
newItem.BeatmapID = newBeatmap.OnlineID;
|
||||||
|
newItem.BeatmapChecksum = newBeatmap.MD5Hash;
|
||||||
|
|
||||||
|
multiplayerClient.EditPlaylistItem(newItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("selected beatmap changed", () => Beatmap.Value.BeatmapInfo.Equals(importedSet2.Beatmaps.First()));
|
||||||
|
AddUntilStep("style selection screen closed", () => this.ChildrenOfType<MultiplayerMatchFreestyleSelect>().SingleOrDefault()?.IsCurrentScreen() != true);
|
||||||
|
}
|
||||||
|
|
||||||
private void enterGameplay()
|
private void enterGameplay()
|
||||||
{
|
{
|
||||||
pressReadyButton();
|
pressReadyButton();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Database;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
@@ -53,10 +54,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||||
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
|
||||||
Dependencies.Cache(Realm);
|
Dependencies.Cache(Realm);
|
||||||
|
Dependencies.CacheAs<BeatmapStore>(new RealmDetachedBeatmapStore());
|
||||||
|
|
||||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
|
||||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||||
|
|
||||||
|
Realm.Write(r =>
|
||||||
|
{
|
||||||
|
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||||
|
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
|
|||||||
@@ -471,7 +471,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
{
|
{
|
||||||
AddUntilStep($"config value is {configValue}", () => getConfigManager().Get<double>(ManiaRulesetSetting.ScrollSpeed), () => Is.EqualTo(configValue));
|
AddUntilStep($"config value is {configValue}", () => getConfigManager().Get<double>(ManiaRulesetSetting.ScrollSpeed), () => Is.EqualTo(configValue));
|
||||||
AddUntilStep($"gameplay value is {gameplayValue}", () => this.ChildrenOfType<DrawableManiaRuleset>().Single().TargetTimeRange,
|
AddUntilStep($"gameplay value is {gameplayValue}", () => this.ChildrenOfType<DrawableManiaRuleset>().Single().TargetTimeRange,
|
||||||
() => Is.EqualTo(DrawableManiaRuleset.ComputeScrollTime(gameplayValue)));
|
() => Is.EqualTo(DrawableManiaRuleset.ComputeScrollTime(gameplayValue, 200, 5)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ManiaRulesetConfigManager getConfigManager() => ((ManiaRulesetConfigManager)Game.Dependencies.Get<IRulesetConfigCache>().GetConfigFor(new ManiaRuleset())!);
|
ManiaRulesetConfigManager getConfigManager() => ((ManiaRulesetConfigManager)Game.Dependencies.Get<IRulesetConfigCache>().GetConfigFor(new ManiaRuleset())!);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ using osu.Game.Rulesets;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Drawables;
|
using osu.Game.Beatmaps.Drawables;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@@ -193,7 +195,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
overlay.ShowBeatmapSet(set);
|
overlay.ShowBeatmapSet(set);
|
||||||
});
|
});
|
||||||
|
|
||||||
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.OnlineID == overlay.Header.RulesetSelector.Current.Value.OnlineID));
|
AddAssert("shown beatmaps of current ruleset",
|
||||||
|
() => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.OnlineID == overlay.Header.RulesetSelector.Current.Value.OnlineID));
|
||||||
AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
|
AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +376,39 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBeatmapsetWithDeletedUser()
|
||||||
|
{
|
||||||
|
AddStep("show map with deleted user", () =>
|
||||||
|
{
|
||||||
|
JObject jsonBlob = JObject.FromObject(getBeatmapSet(), new JsonSerializer
|
||||||
|
{
|
||||||
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
|
});
|
||||||
|
|
||||||
|
jsonBlob["user"] = JToken.Parse(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"avatar_url": null,
|
||||||
|
"country_code": null,
|
||||||
|
"default_group": "default",
|
||||||
|
"id": null,
|
||||||
|
"is_active": false,
|
||||||
|
"is_bot": false,
|
||||||
|
"is_deleted": true,
|
||||||
|
"is_online": false,
|
||||||
|
"is_supporter": false,
|
||||||
|
"last_visit": null,
|
||||||
|
"pm_friends_only": false,
|
||||||
|
"profile_colour": null,
|
||||||
|
"username": "[deleted user]"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
overlay.ShowBeatmapSet(JsonConvert.DeserializeObject<APIBeatmapSet>(JsonConvert.SerializeObject(jsonBlob)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private APIBeatmapSet createManyDifficultiesBeatmapSet()
|
private APIBeatmapSet createManyDifficultiesBeatmapSet()
|
||||||
{
|
{
|
||||||
var set = getBeatmapSet();
|
var set = getBeatmapSet();
|
||||||
|
|||||||
@@ -274,6 +274,29 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddUntilStep("all panels have non-negative position", () => this.ChildrenOfType<ScorePanel>().All(p => p.ScorePosition.Value > 0));
|
AddUntilStep("all panels have non-negative position", () => this.ChildrenOfType<ScorePanel>().All(p => p.ScorePosition.Value > 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPresentInvalidOnlineScore()
|
||||||
|
{
|
||||||
|
AddStep("set up invalid user score", () =>
|
||||||
|
{
|
||||||
|
userScore.OnlineID = -1;
|
||||||
|
userScore.TotalScore = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
|
||||||
|
|
||||||
|
createResultsWithScore(() => userScore);
|
||||||
|
|
||||||
|
AddUntilStep("wait for user score to be displayed", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Any());
|
||||||
|
AddWaitStep("wait for any more potential scores", 5);
|
||||||
|
AddAssert("only 1 score visible", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Count(), () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddUntilStep("left loading spinner hidden", () =>
|
||||||
|
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
|
||||||
|
AddUntilStep("right loading spinner hidden", () =>
|
||||||
|
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
private void createResultsWithScore(Func<ScoreInfo> getScore)
|
private void createResultsWithScore(Func<ScoreInfo> getScore)
|
||||||
{
|
{
|
||||||
AddStep("load results", () =>
|
AddStep("load results", () =>
|
||||||
@@ -359,7 +382,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
switch (request)
|
switch (request)
|
||||||
{
|
{
|
||||||
case ShowPlaylistScoreRequest s:
|
case ShowPlaylistScoreRequest s:
|
||||||
if (userScore == null)
|
if (userScore == null || userScore.OnlineID == -1)
|
||||||
triggerFail(s);
|
triggerFail(s);
|
||||||
else
|
else
|
||||||
triggerSuccess(s, () => createUserResponse(userScore));
|
triggerSuccess(s, () => createUserResponse(userScore));
|
||||||
@@ -367,7 +390,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case ShowPlaylistUserScoreRequest u:
|
case ShowPlaylistUserScoreRequest u:
|
||||||
if (userScore == null)
|
if (userScore == null || userScore.OnlineID == -1)
|
||||||
triggerFail(u);
|
triggerFail(u);
|
||||||
else
|
else
|
||||||
triggerSuccess(u, () => createUserResponse(userScore));
|
triggerSuccess(u, () => createUserResponse(userScore));
|
||||||
|
|||||||
@@ -333,22 +333,32 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Source grouping
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task TestGroupingBySource()
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
var beatmapSets = new List<BeatmapSetInfo>();
|
||||||
|
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Cool Game", beatmapSets, out var beatmapCoolGame);
|
||||||
|
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Cool game", beatmapSets, out var beatmapCoolGameB);
|
||||||
|
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = "Nice Movie", beatmapSets, out var beatmapNiceMovie);
|
||||||
|
addBeatmapSet(s => s.Beatmaps[0].Metadata.Source = string.Empty, beatmapSets, out var beatmapUnsourced);
|
||||||
|
|
||||||
|
var results = await runGrouping(GroupMode.Source, beatmapSets);
|
||||||
|
assertGroup(results, 0, "Cool Game", new[] { beatmapCoolGame, beatmapCoolGameB }, ref total);
|
||||||
|
assertGroup(results, 1, "Nice Movie", new[] { beatmapNiceMovie }, ref total);
|
||||||
|
assertGroup(results, 2, "Unsourced", new[] { beatmapUnsourced }, ref total);
|
||||||
|
assertTotal(results, total);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
private static async Task<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
private static async Task<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
||||||
{
|
{
|
||||||
var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group });
|
var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group });
|
||||||
var carouselItems = await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
return await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
||||||
|
|
||||||
// sanity check to ensure no detection of two group items with equal order value.
|
|
||||||
var groups = carouselItems.Select(i => i.Model).OfType<GroupDefinition>();
|
|
||||||
|
|
||||||
foreach (var header in groups)
|
|
||||||
{
|
|
||||||
var sameOrder = groups.FirstOrDefault(g => g != header && g.Order == header.Order);
|
|
||||||
if (sameOrder != null)
|
|
||||||
Assert.Fail($"Detected two groups with equal order number: \"{header.Title}\" vs. \"{sameOrder.Title}\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
return carouselItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
|
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@@ -145,14 +146,27 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
|||||||
AddUntilStep("wait for filtering", () => !Carousel.IsFiltering);
|
AddUntilStep("wait for filtering", () => !Carousel.IsFiltering);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ImportBeatmapForRuleset(int rulesetId)
|
protected void SortBy(SortMode mode) => AddStep($"sort by {mode.GetDescription().ToLowerInvariant()}", () => Config.SetValue(OsuSetting.SongSelectSortingMode, mode));
|
||||||
|
|
||||||
|
protected void GroupBy(GroupMode mode) => AddStep($"group by {mode.GetDescription().ToLowerInvariant()}", () => Config.SetValue(OsuSetting.SongSelectGroupMode, mode));
|
||||||
|
|
||||||
|
protected void SortAndGroupBy(SortMode sort, GroupMode group)
|
||||||
|
{
|
||||||
|
AddStep($"sort by {sort.GetDescription().ToLowerInvariant()} & group by {group.GetDescription().ToLowerInvariant()}", () =>
|
||||||
|
{
|
||||||
|
Config.SetValue(OsuSetting.SongSelectSortingMode, sort);
|
||||||
|
Config.SetValue(OsuSetting.SongSelectGroupMode, group);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ImportBeatmapForRuleset(params int[] rulesetIds)
|
||||||
{
|
{
|
||||||
int beatmapsCount = 0;
|
int beatmapsCount = 0;
|
||||||
|
|
||||||
AddStep($"import test map for ruleset {rulesetId}", () =>
|
AddStep($"import test map for ruleset {rulesetIds}", () =>
|
||||||
{
|
{
|
||||||
beatmapsCount = SongSelect.IsNull() ? 0 : Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().Single().SetItems.Count;
|
beatmapsCount = SongSelect.IsNull() ? 0 : Carousel.Filters.OfType<BeatmapCarouselFilterGrouping>().Single().SetItems.Count;
|
||||||
Beatmaps.Import(TestResources.CreateTestBeatmapSetInfo(3, Rulesets.AvailableRulesets.Where(r => r.OnlineID == rulesetId).ToArray()));
|
Beatmaps.Import(TestResources.CreateTestBeatmapSetInfo(3, Rulesets.AvailableRulesets.Where(r => rulesetIds.Contains(r.OnlineID)).ToArray()));
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is specifically for cases where the add is happening post song select load.
|
// This is specifically for cases where the add is happening post song select load.
|
||||||
|
|||||||
@@ -162,5 +162,23 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
|||||||
AddAssert("statistics still visible", () => display.ChildrenOfType<BeatmapTitleWedge.StatisticDifficulty>().First().Parent!.Alpha == 1);
|
AddAssert("statistics still visible", () => display.ChildrenOfType<BeatmapTitleWedge.StatisticDifficulty>().First().Parent!.Alpha == 1);
|
||||||
AddAssert("tiny statistics still hidden", () => display.ChildrenOfType<GridContainer>().Last().Alpha == 0);
|
AddAssert("tiny statistics still hidden", () => display.ChildrenOfType<GridContainer>().Last().Alpha == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMaximumLength()
|
||||||
|
{
|
||||||
|
AddStep("setup auto size", () => Child = display = new BeatmapTitleWedge.DifficultyStatisticsDisplay(true)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("set long statistics", () => display.Statistics = new[]
|
||||||
|
{
|
||||||
|
new BeatmapTitleWedge.StatisticDifficulty.Data("Very Long Statistic 1", 0.2f, 0.2f, 1f),
|
||||||
|
new BeatmapTitleWedge.StatisticDifficulty.Data("Very Long Statistic 2", 0.7f, 0.7f, 1f),
|
||||||
|
new BeatmapTitleWedge.StatisticDifficulty.Data("Very Long Statistic 3", 0.4f, 0.8f, 1f),
|
||||||
|
new BeatmapTitleWedge.StatisticDifficulty.Data("Very Long Statistic 4", 0.3f, 0.3f, 1f),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,253 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Screens.Select.Filter;
|
||||||
|
using osu.Game.Screens.SelectV2;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The fallback behaviour guaranteed by SongSelect is that a random selection will happen in worst case scenario.
|
||||||
|
/// Every case we're testing here is expected to have a *custom behaviour* – engaging and overriding this random selection fallback.
|
||||||
|
///
|
||||||
|
/// The scenarios we care abouts are:
|
||||||
|
/// - Ruleset change (select another difficulty from same set for the new ruleset, if possible).
|
||||||
|
/// - Beatmap difficulty hidden (select closest valid difficulty from same set)
|
||||||
|
/// - Beatmap set deleted (select closest valid beatmap post-deletion)
|
||||||
|
///
|
||||||
|
/// We are working with 5 sets, each with 3 difficulties (all osu! ruleset).
|
||||||
|
/// </summary>
|
||||||
|
public partial class TestSceneSongSelectCurrentSelectionInvalidated : SongSelectTestScene
|
||||||
|
{
|
||||||
|
private BeatmapInfo? selectedBeatmap => (BeatmapInfo?)Carousel.CurrentSelection;
|
||||||
|
private BeatmapSetInfo? selectedBeatmapSet => selectedBeatmap?.BeatmapSet;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
ImportBeatmapForRuleset(0);
|
||||||
|
|
||||||
|
LoadSongSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRulesetChange()
|
||||||
|
{
|
||||||
|
AddStep("disable converts", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
|
|
||||||
|
ImportBeatmapForRuleset(0, 1);
|
||||||
|
ImportBeatmapForRuleset(0, 1);
|
||||||
|
ImportBeatmapForRuleset(0, 2);
|
||||||
|
waitForFiltering(5);
|
||||||
|
|
||||||
|
ChangeRuleset(1);
|
||||||
|
waitForFiltering(6);
|
||||||
|
|
||||||
|
BeatmapInfo? initiallySelected = null;
|
||||||
|
AddAssert("selected is taiko", () => (initiallySelected = selectedBeatmap)?.Ruleset.OnlineID, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
ChangeRuleset(0);
|
||||||
|
waitForFiltering(7);
|
||||||
|
AddAssert("selected is osu", () => selectedBeatmap?.Ruleset.OnlineID, () => Is.EqualTo(0));
|
||||||
|
AddAssert("selected is same set as original", () => selectedBeatmap?.BeatmapSet, () => Is.EqualTo(initiallySelected!.BeatmapSet));
|
||||||
|
|
||||||
|
ChangeRuleset(1);
|
||||||
|
waitForFiltering(8);
|
||||||
|
AddAssert("selected is taiko", () => selectedBeatmap?.Ruleset.OnlineID, () => Is.EqualTo(1));
|
||||||
|
AddAssert("selected is same set as original", () => selectedBeatmap?.BeatmapSet, () => Is.EqualTo(initiallySelected!.BeatmapSet));
|
||||||
|
|
||||||
|
ChangeRuleset(2);
|
||||||
|
waitForFiltering(9);
|
||||||
|
AddAssert("selected is catch", () => selectedBeatmap?.Ruleset.OnlineID, () => Is.EqualTo(2));
|
||||||
|
AddAssert("selected is different set", () => selectedBeatmap?.BeatmapSet, () => Is.Not.EqualTo(initiallySelected!.BeatmapSet));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Make sure that deleting all sets doesn't hit some weird edge case / crash.
|
||||||
|
/// </summary>
|
||||||
|
[TestCase(SortMode.Title)]
|
||||||
|
[TestCase(SortMode.Artist)]
|
||||||
|
[TestCase(SortMode.Difficulty)]
|
||||||
|
public void TestDeleteAllSets(SortMode sortMode)
|
||||||
|
{
|
||||||
|
int filterCount = sortMode != SortMode.Title ? 2 : 1;
|
||||||
|
|
||||||
|
SortBy(sortMode);
|
||||||
|
waitForFiltering(filterCount);
|
||||||
|
|
||||||
|
BeatmapSetInfo deletedSet = null!;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(filterCount + 1 + i);
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The carousel still holds an invalid selection after the final deletion. Probably fine?
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
AddUntilStep("wait for no global selection", () => Beatmap.IsDefault, () => Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DifficultiesGrouped_DeleteSet_SelectsAdjacent()
|
||||||
|
{
|
||||||
|
SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty);
|
||||||
|
waitForFiltering(2);
|
||||||
|
|
||||||
|
makePanelSelected<PanelGroupStarDifficulty>(2);
|
||||||
|
makePanelSelected<PanelBeatmapStandalone>(3);
|
||||||
|
|
||||||
|
// Deleting second-last, should select last
|
||||||
|
BeatmapSetInfo deletedSet = null!;
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(3);
|
||||||
|
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
assertPanelSelected<PanelBeatmapStandalone>(3);
|
||||||
|
|
||||||
|
// Deleting last, should select previous
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(4);
|
||||||
|
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
assertPanelSelected<PanelBeatmapStandalone>(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(SortMode.Title)]
|
||||||
|
[TestCase(SortMode.Artist)]
|
||||||
|
public void SetsGrouped_DeleteSet_SelectsAdjacent(SortMode sortMode)
|
||||||
|
{
|
||||||
|
int filterCount = sortMode != SortMode.Title ? 2 : 1;
|
||||||
|
|
||||||
|
SortBy(sortMode);
|
||||||
|
waitForFiltering(filterCount);
|
||||||
|
|
||||||
|
makePanelSelected<PanelBeatmapSet>(3);
|
||||||
|
|
||||||
|
// Deleting second-last, should select last
|
||||||
|
BeatmapSetInfo deletedSet = null!;
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(filterCount + 1);
|
||||||
|
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
assertPanelSelected<PanelBeatmapSet>(3);
|
||||||
|
assertPanelSelected<PanelBeatmap>(0);
|
||||||
|
|
||||||
|
// Deleting last, should select previous
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(filterCount + 2);
|
||||||
|
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
assertPanelSelected<PanelBeatmapSet>(2);
|
||||||
|
assertPanelSelected<PanelBeatmap>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same scenario as the test case above, but where selected difficulty before deletion is not first index in the expanded set.
|
||||||
|
// Basically ensures that the reselection is running `RequestRecommendedSelection` and not just relying on indices.
|
||||||
|
[TestCase(SortMode.Title)]
|
||||||
|
[TestCase(SortMode.Artist)]
|
||||||
|
public void SetsGrouped_DeleteSet_SelectsNextSetRecommendedDifficulty(SortMode sortMode)
|
||||||
|
{
|
||||||
|
int filterCount = sortMode != SortMode.Title ? 2 : 1;
|
||||||
|
|
||||||
|
SortBy(sortMode);
|
||||||
|
waitForFiltering(filterCount);
|
||||||
|
|
||||||
|
makePanelSelected<PanelBeatmapSet>(2);
|
||||||
|
makePanelSelected<PanelBeatmap>(2);
|
||||||
|
|
||||||
|
AddUntilStep("wait for beatmap to be selected", () => selectedBeatmapSet != null);
|
||||||
|
|
||||||
|
BeatmapSetInfo deletedSet = null!;
|
||||||
|
AddStep("delete selected", () => Beatmaps.Delete(deletedSet = selectedBeatmapSet!));
|
||||||
|
waitForFiltering(++filterCount);
|
||||||
|
|
||||||
|
selectionChangedFrom(() => deletedSet);
|
||||||
|
assertPanelSelected<PanelBeatmapSet>(2);
|
||||||
|
assertPanelSelected<PanelBeatmap>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHideBeatmap()
|
||||||
|
{
|
||||||
|
makePanelSelected<PanelBeatmapSet>(2);
|
||||||
|
makePanelSelected<PanelBeatmap>(1);
|
||||||
|
|
||||||
|
BeatmapInfo hiddenBeatmap = null!;
|
||||||
|
|
||||||
|
AddStep("hide selected", () => Beatmaps.Hide(hiddenBeatmap = selectedBeatmap!));
|
||||||
|
waitForFiltering(2);
|
||||||
|
|
||||||
|
AddAssert("selected beatmap below", () => selectedBeatmap!.BeatmapSet, () => Is.EqualTo(hiddenBeatmap.BeatmapSet));
|
||||||
|
|
||||||
|
AddStep("hide selected", () => Beatmaps.Hide(hiddenBeatmap = selectedBeatmap!));
|
||||||
|
waitForFiltering(3);
|
||||||
|
|
||||||
|
AddAssert("selected beatmap below", () => selectedBeatmap!.BeatmapSet, () => Is.EqualTo(hiddenBeatmap.BeatmapSet));
|
||||||
|
assertPanelSelected<PanelBeatmap>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForFiltering(int filterCount = 1)
|
||||||
|
{
|
||||||
|
AddUntilStep("wait for filter count", () => Carousel.FilterCount, () => Is.EqualTo(filterCount));
|
||||||
|
AddUntilStep("filtering finished", () => Carousel.IsFiltering, () => Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makePanelSelected<T>(int index)
|
||||||
|
where T : Panel
|
||||||
|
{
|
||||||
|
AddStep($"click panel at index {index} if not selected", () =>
|
||||||
|
{
|
||||||
|
var panel = allPanels<T>().ElementAt(index).ChildrenOfType<Panel>().Single();
|
||||||
|
|
||||||
|
// May have already been selected randomly. Don't click a second time or gameplay will start.
|
||||||
|
if (!panel.Selected.Value)
|
||||||
|
panel.TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
assertPanelSelected<T>(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectionChangedFrom(Func<BeatmapSetInfo> deletedSet) =>
|
||||||
|
AddUntilStep("selection changed", () => selectedBeatmapSet, () => Is.Not.EqualTo(deletedSet()));
|
||||||
|
|
||||||
|
private void assertPanelSelected<T>(int index)
|
||||||
|
where T : Panel
|
||||||
|
=> AddUntilStep($"selected panel at index {index}", getActivePanelIndex<T>, () => Is.EqualTo(index));
|
||||||
|
|
||||||
|
private int getActivePanelIndex<T>()
|
||||||
|
where T : Panel
|
||||||
|
=> allPanels<T>().ToList().FindIndex(p =>
|
||||||
|
{
|
||||||
|
switch (p)
|
||||||
|
{
|
||||||
|
case PanelBeatmapStandalone pb:
|
||||||
|
return pb.Selected.Value;
|
||||||
|
|
||||||
|
case PanelBeatmap pb:
|
||||||
|
return pb.Selected.Value;
|
||||||
|
|
||||||
|
case Panel pbs:
|
||||||
|
return pbs.Expanded.Value;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private IEnumerable<T> allPanels<T>()
|
||||||
|
where T : Panel
|
||||||
|
=> Carousel.ChildrenOfType<T>().Where(p => p.Item != null).OrderBy(p => p.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -117,14 +117,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
|||||||
// TODO: old test has this step, but there doesn't seem to be any purpose for it.
|
// TODO: old test has this step, but there doesn't seem to be any purpose for it.
|
||||||
// AddUntilStep("random map selected", () => Beatmap.Value != defaultBeatmap);
|
// AddUntilStep("random map selected", () => Beatmap.Value != defaultBeatmap);
|
||||||
|
|
||||||
AddStep(@"Sort by Artist", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Artist));
|
SortBy(SortMode.Artist);
|
||||||
AddStep(@"Sort by Title", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title));
|
SortBy(SortMode.Title);
|
||||||
AddStep(@"Sort by Author", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Author));
|
SortBy(SortMode.Author);
|
||||||
AddStep(@"Sort by DateAdded", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.DateAdded));
|
SortBy(SortMode.DateAdded);
|
||||||
AddStep(@"Sort by BPM", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.BPM));
|
SortBy(SortMode.BPM);
|
||||||
AddStep(@"Sort by Length", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Length));
|
SortBy(SortMode.Length);
|
||||||
AddStep(@"Sort by Difficulty", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Difficulty));
|
SortBy(SortMode.Difficulty);
|
||||||
AddStep(@"Sort by Source", () => Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Source));
|
SortBy(SortMode.Source);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -309,6 +309,24 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
|||||||
checkMatchedBeatmaps(3);
|
checkMatchedBeatmaps(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCantHideAllBeatmaps()
|
||||||
|
{
|
||||||
|
LoadSongSelect();
|
||||||
|
ImportBeatmapForRuleset(0);
|
||||||
|
|
||||||
|
checkMatchedBeatmaps(3);
|
||||||
|
|
||||||
|
AddStep("hide selected", () => Beatmaps.Hide(Beatmap.Value.BeatmapInfo));
|
||||||
|
checkMatchedBeatmaps(2);
|
||||||
|
|
||||||
|
AddStep("hide selected", () => Beatmaps.Hide(Beatmap.Value.BeatmapInfo));
|
||||||
|
checkMatchedBeatmaps(1);
|
||||||
|
|
||||||
|
AddAssert("hide fails", () => Beatmaps.Hide(Beatmap.Value.BeatmapInfo), () => Is.False);
|
||||||
|
checkMatchedBeatmaps(1);
|
||||||
|
}
|
||||||
|
|
||||||
private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||||
|
|
||||||
private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using osu.Game.Graphics;
|
|||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Tournament.IPC;
|
using osu.Game.Tournament.IPC;
|
||||||
@@ -42,6 +43,7 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private TournamentSceneManager? sceneManager { get; set; }
|
private TournamentSceneManager? sceneManager { get; set; }
|
||||||
|
|
||||||
|
private readonly IBindable<APIUser> localUser = new Bindable<APIUser>();
|
||||||
private Bindable<Size> windowSize = null!;
|
private Bindable<Size> windowSize = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@@ -70,7 +72,8 @@ namespace osu.Game.Tournament.Screens.Setup
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
api.LocalUser.BindValueChanged(_ => Schedule(reload));
|
localUser.BindTo(api.LocalUser);
|
||||||
|
localUser.BindValueChanged(_ => Schedule(reload));
|
||||||
stableInfo.OnStableInfoSaved += () => Schedule(reload);
|
stableInfo.OnStableInfoSaved += () => Schedule(reload);
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,24 +218,37 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a beatmap difficulty.
|
/// Hide a beatmap difficulty.
|
||||||
|
/// Will fail if all difficulties are about to be hidden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
|
/// <param name="beatmapInfo">The beatmap difficulty to hide.</param>
|
||||||
public void Hide(BeatmapInfo beatmapInfo)
|
public bool Hide(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
Realm.Run(r =>
|
return Realm.Run(r =>
|
||||||
{
|
{
|
||||||
using (var transaction = r.BeginWrite())
|
using (var transaction = r.BeginWrite())
|
||||||
{
|
{
|
||||||
if (!beatmapInfo.IsManaged)
|
if (!beatmapInfo.IsManaged)
|
||||||
beatmapInfo = r.Find<BeatmapInfo>(beatmapInfo.ID)!;
|
beatmapInfo = r.Find<BeatmapInfo>(beatmapInfo.ID)!;
|
||||||
|
|
||||||
|
if (!CanHide(beatmapInfo))
|
||||||
|
return false;
|
||||||
|
|
||||||
beatmapInfo.Hidden = true;
|
beatmapInfo.Hidden = true;
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanHide(BeatmapInfo beatmapInfo) => Realm.Run(r =>
|
||||||
|
{
|
||||||
|
if (!beatmapInfo.IsManaged)
|
||||||
|
beatmapInfo = r.Find<BeatmapInfo>(beatmapInfo.ID)!;
|
||||||
|
|
||||||
|
return beatmapInfo.BeatmapSet!.Beatmaps.Count(b => !b.Hidden) > 1;
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restore a beatmap difficulty.
|
/// Restore a beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
public virtual MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
{
|
{
|
||||||
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, Action),
|
new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, Action),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapSet;
|
using osu.Game.Overlays.BeatmapSet;
|
||||||
@@ -321,5 +325,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
buttonContainer.ShowDetails.Value = showDetails;
|
buttonContainer.ShowDetails.Value = showDetails;
|
||||||
thumbnail.Dimmed.Value = showDetails;
|
thumbnail.Dimmed.Value = showDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var items = base.ContextMenuItems.ToList();
|
||||||
|
|
||||||
|
foreach (var button in buttonContainer.Buttons)
|
||||||
|
{
|
||||||
|
if (button.Enabled.Value)
|
||||||
|
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
@@ -165,5 +169,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
buttonContainer.ShowDetails.Value = showDetails;
|
buttonContainer.ShowDetails.Value = showDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var items = base.ContextMenuItems.ToList();
|
||||||
|
|
||||||
|
foreach (var button in buttonContainer.Buttons)
|
||||||
|
{
|
||||||
|
if (button.Enabled.Value)
|
||||||
|
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,18 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapSet;
|
using osu.Game.Overlays.BeatmapSet;
|
||||||
@@ -291,5 +295,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override MenuItem[] ContextMenuItems
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var items = base.ContextMenuItems.ToList();
|
||||||
|
|
||||||
|
foreach (var button in buttonContainer.Buttons)
|
||||||
|
{
|
||||||
|
if (button.Enabled.Value)
|
||||||
|
items.Add(new OsuMenuItem(button.TooltipText.ToSentence(), MenuItemType.Standard, () => button.TriggerClick()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
|
|||||||
|
|
||||||
private void updateState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
Enabled.Value = state.Value == DownloadState.LocallyAvailable;
|
||||||
|
this.FadeTo(Enabled.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@@ -48,6 +49,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<BeatmapCardIconButton> Buttons => buttons;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => mainContent;
|
protected override Container<Drawable> Content => mainContent;
|
||||||
|
|
||||||
private readonly Container background;
|
private readonly Container background;
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ruleset ruleset = displayedContent.Ruleset.CreateInstance();
|
Ruleset ruleset = displayedContent.Ruleset.CreateInstance();
|
||||||
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
|
BeatmapDifficulty adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(originalDifficulty, displayedContent.Mods ?? []);
|
||||||
|
|
||||||
circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##");
|
circleSize.Text = @"CS: " + adjustedDifficulty.CircleSize.ToString(@"0.##");
|
||||||
drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##");
|
drainRate.Text = @" HP: " + adjustedDifficulty.DrainRate.ToString(@"0.##");
|
||||||
|
|||||||
@@ -102,8 +102,6 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 49;
|
private const int schema_version = 49;
|
||||||
|
|
||||||
public static int SchemaVersion => schema_version;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ namespace osu.Game.Graphics.Carousel
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a list of all <see cref="CarouselItem"/>s currently displayed.
|
/// Retrieve a list of all <see cref="CarouselItem"/>s currently displayed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyCollection<CarouselItem>? GetCarouselItems() => carouselItems;
|
public IList<CarouselItem>? GetCarouselItems() => carouselItems;
|
||||||
|
|
||||||
private List<CarouselItem>? carouselItems;
|
private List<CarouselItem>? carouselItems;
|
||||||
|
|
||||||
@@ -691,6 +691,11 @@ namespace osu.Game.Graphics.Carousel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected CarouselItem? CurrentSelectionItem => currentSelection.CarouselItem;
|
protected CarouselItem? CurrentSelectionItem => currentSelection.CarouselItem;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The index in <see cref="GetCarouselItems"/> of the current selection, if available.
|
||||||
|
/// </summary>
|
||||||
|
protected int? CurrentSelectionIndex => currentSelection.Index;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Becomes invalid when the current selection has changed and needs to be updated visually.
|
/// Becomes invalid when the current selection has changed and needs to be updated visually.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@@ -14,23 +12,27 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
public partial class OsuTextFlowContainer : TextFlowContainer
|
public partial class OsuTextFlowContainer : TextFlowContainer
|
||||||
{
|
{
|
||||||
public OsuTextFlowContainer(Action<SpriteText> defaultCreationParameters = null)
|
public OsuTextFlowContainer(Action<SpriteText>? defaultCreationParameters = null)
|
||||||
: base(defaultCreationParameters)
|
: base(defaultCreationParameters)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
protected override SpriteText CreateSpriteText() => new OsuSpriteText();
|
||||||
|
|
||||||
public ITextPart AddArbitraryDrawable(Drawable drawable) => AddPart(new TextPartManual(new ArbitraryDrawableWrapper { Child = drawable }.Yield()));
|
public ITextPart AddArbitraryDrawable(Drawable drawable) => AddPart(new TextPartManual(new ArbitraryDrawableWrapper(drawable).Yield()));
|
||||||
|
|
||||||
public ITextPart AddIcon(IconUsage icon, Action<SpriteText> creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters);
|
public ITextPart AddIcon(IconUsage icon, Action<SpriteText>? creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters);
|
||||||
|
|
||||||
private partial class ArbitraryDrawableWrapper : Container, IHasLineBaseHeight
|
private partial class ArbitraryDrawableWrapper : Container, IHasLineBaseHeight
|
||||||
{
|
{
|
||||||
public float LineBaseHeight => (Child as IHasLineBaseHeight)?.LineBaseHeight ?? DrawHeight;
|
private readonly IHasLineBaseHeight? lineBaseHeightSource;
|
||||||
|
|
||||||
public ArbitraryDrawableWrapper()
|
public float LineBaseHeight => lineBaseHeightSource?.LineBaseHeight ?? DrawHeight;
|
||||||
|
|
||||||
|
public ArbitraryDrawableWrapper(Drawable drawable)
|
||||||
{
|
{
|
||||||
|
Child = drawable;
|
||||||
|
lineBaseHeightSource = drawable as IHasLineBaseHeight;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ namespace osu.Game.Localisation
|
|||||||
private const string prefix = @"osu.Game.Resources.Localisation.MenuTip";
|
private const string prefix = @"osu.Game.Resources.Localisation.MenuTip";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Press Ctrl-T anywhere in the game to toggle the toolbar!"
|
/// "Press {0} anywhere in the game to toggle the toolbar!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleToolbarShortcut => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press Ctrl-T anywhere in the game to toggle the toolbar!");
|
public static LocalisableString ToggleToolbarShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"toggle_toolbar_shortcut"), @"Press {0} anywhere in the game to toggle the toolbar!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Press Ctrl-O anywhere in the game to access settings!"
|
/// "Press {0} anywhere in the game to access settings!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString GameSettingsShortcut => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press Ctrl-O anywhere in the game to access settings!");
|
public static LocalisableString GameSettingsShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"game_settings_shortcut"), @"Press {0} anywhere in the game to access settings!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!"
|
/// "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!"
|
||||||
@@ -40,9 +40,9 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString ScreenScalingSettings => new TranslatableString(getKey(@"screen_scaling_settings"), @"Try adjusting the ""Screen Scaling"" mode to change your gameplay or UI area, even in fullscreen!");
|
public static LocalisableString ScreenScalingSettings => new TranslatableString(getKey(@"screen_scaling_settings"), @"Try adjusting the ""Screen Scaling"" mode to change your gameplay or UI area, even in fullscreen!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using Ctrl-B!"
|
/// "What used to be "osu!direct" is available to all users just like on the website. You can access it anywhere using {0}!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString FreeOsuDirect => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using Ctrl-B!");
|
public static LocalisableString FreeOsuDirect(LocalisableString keybind) => new TranslatableString(getKey(@"free_osu_direct"), @"What used to be ""osu!direct"" is available to all users just like on the website. You can access it anywhere using {0}!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!"
|
/// "Seeking in replays is available by dragging on the progress bar at the bottom of the screen or by using the left and right arrow keys!"
|
||||||
@@ -75,9 +75,9 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString ToggleAdvancedFPSCounter => new TranslatableString(getKey(@"toggle_advanced_fps_counter"), @"Toggle advanced frame / thread statistics with Ctrl-F11!");
|
public static LocalisableString ToggleAdvancedFPSCounter => new TranslatableString(getKey(@"toggle_advanced_fps_counter"), @"Toggle advanced frame / thread statistics with Ctrl-F11!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You can pause during a replay by pressing Space!"
|
/// "You can pause during a replay by pressing {0}!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ReplayPausing => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing Space!");
|
public static LocalisableString ReplayPausing(LocalisableString keybind) => new TranslatableString(getKey(@"replay_pausing"), @"You can pause during a replay by pressing {0}!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"
|
/// "Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!"
|
||||||
@@ -85,9 +85,9 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!");
|
public static LocalisableString ConfigurableHotkeys => new TranslatableString(getKey(@"configurable_hotkeys"), @"Most of the hotkeys in the game are configurable and can be changed to anything you want. Check the bindings panel under input settings!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!"
|
/// "Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via {0}!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via Ctrl-Shift-S!");
|
public static LocalisableString SkinEditor(LocalisableString keybind) => new TranslatableString(getKey(@"skin_editor"), @"Your gameplay HUD can be customised by using the skin layout editor. Open it at any time via {0}!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You can create mod presets to make toggling your favourite mod combinations easier!"
|
/// "You can create mod presets to make toggling your favourite mod combinations easier!"
|
||||||
@@ -100,14 +100,14 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!");
|
public static LocalisableString ModCustomisationSettings => new TranslatableString(getKey(@"mod_customisation_settings"), @"Many mods have customisation settings that drastically change how they function. Click the Customise button in mod select to view settings!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Press Ctrl-Shift-R to switch to a random skin!"
|
/// "Press {0} to switch to a random skin!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RandomSkinShortcut => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press Ctrl-Shift-R to switch to a random skin!");
|
public static LocalisableString RandomSkinShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"random_skin_shortcut"), @"Press {0} to switch to a random skin!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "While watching a replay, press Ctrl-H to toggle replay settings!"
|
/// "While watching a replay, press {0} to toggle replay settings!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleReplaySettingsShortcut => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press Ctrl-H to toggle replay settings!");
|
public static LocalisableString ToggleReplaySettingsShortcut(LocalisableString keybind) => new TranslatableString(getKey(@"toggle_replay_settings_shortcut"), @"While watching a replay, press {0} to toggle replay settings!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "You can easily copy the mods from scores on a leaderboard by right-clicking on them!"
|
/// "You can easily copy the mods from scores on a leaderboard by right-clicking on them!"
|
||||||
@@ -140,9 +140,9 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString GlobalStatisticsShortcut => new TranslatableString(getKey(@"global_statistics_shortcut"), @"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!");
|
public static LocalisableString GlobalStatisticsShortcut => new TranslatableString(getKey(@"global_statistics_shortcut"), @"Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!"
|
/// "When your gameplay HUD is hidden, you can press and hold {0} to view it temporarily!"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString PeekHUDWhenHidden => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold Ctrl to view it temporarily!");
|
public static LocalisableString PeekHUDWhenHidden(LocalisableString keybind) => new TranslatableString(getKey(@"peek_hud_when_hidden"), @"When your gameplay HUD is hidden, you can press and hold {0} to view it temporarily!", keybind);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Drag and drop any image into the skin editor to load it in quickly!"
|
/// "Drag and drop any image into the skin editor to load it in quickly!"
|
||||||
|
|||||||
@@ -55,9 +55,29 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString EditBeatmap => new TranslatableString(getKey(@"edit_beatmap"), @"Edit beatmap");
|
public static LocalisableString EditBeatmap => new TranslatableString(getKey(@"edit_beatmap"), @"Edit beatmap");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "{0} stars"
|
/// "Circle Size"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Stars(LocalisableString value) => new TranslatableString(getKey(@"stars"), @"{0} stars", value);
|
public static LocalisableString CircleSize => new TranslatableString(getKey(@"circle_size"), @"Circle Size");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Key Count"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString KeyCount => new TranslatableString(getKey(@"key_count"), @"Key Count");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Approach Rate"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ApproachRate => new TranslatableString(getKey(@"approach_rate"), @"Approach Rate");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Accuracy"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Accuracy => new TranslatableString(getKey(@"accuracy"), @"Accuracy");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "HP Drain"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString HPDrain => new TranslatableString(getKey(@"hp_drain"), @"HP Drain");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Submitted"
|
/// "Submitted"
|
||||||
@@ -69,6 +89,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Ranked => new TranslatableString(getKey(@"ranked"), @"Ranked");
|
public static LocalisableString Ranked => new TranslatableString(getKey(@"ranked"), @"Ranked");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "{0} stars"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Stars(LocalisableString value) => new TranslatableString(getKey(@"stars"), @"{0} stars", value);
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,11 +84,26 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
/// The creator of this beatmap set.
|
/// The creator of this beatmap set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This is not included when the set is retrieved via <see cref="SearchBeatmapSetsRequest"/>,
|
/// This property is set differently depending on the API endpoint. When retrieved via <see cref="SearchBeatmapSetsRequest"/>,
|
||||||
/// but the creator's ID and username will be filled in this property from the <see cref="AuthorID"/> and <see cref="AuthorString"/> properties.
|
/// detailed user info is not included and the creator's ID and username are filled from the <see cref="AuthorID"/> and
|
||||||
|
/// <see cref="AuthorString"/> properties. For other API endpoints, this property is set by the <see cref="author"/> setter.
|
||||||
|
/// </remarks>
|
||||||
|
public APIUser Author = new APIUser();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper property to deserialize the detailed user info to <see cref="Author"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This setter implements special handling for deleted users. When received a user with ID 1, it indicates
|
||||||
|
/// the original user has been deleted. In such cases, the existing <see cref="Author"/> data
|
||||||
|
/// (filled from <see cref="AuthorID"/> and <see cref="AuthorString"/>) is preserved. For valid user,
|
||||||
|
/// the provided user info replaces the existing <see cref="Author"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonProperty(@"user")]
|
[JsonProperty(@"user")]
|
||||||
public APIUser Author = new APIUser();
|
private APIUser author
|
||||||
|
{
|
||||||
|
set => Author = value.Id != 1 ? value : Author;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the beatmap set's creator.
|
/// The ID of the beatmap set's creator.
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int SYSTEM_USER_ID = 0;
|
public const int SYSTEM_USER_ID = 0;
|
||||||
|
|
||||||
[JsonProperty(@"id")]
|
// In osu-web, deleted users have a null ID. When deserializing, we ignore the null value and use 1 instead.
|
||||||
|
[JsonProperty(@"id", NullValueHandling = NullValueHandling.Ignore)]
|
||||||
public int Id { get; set; } = 1;
|
public int Id { get; set; } = 1;
|
||||||
|
|
||||||
[JsonProperty(@"join_date")]
|
[JsonProperty(@"join_date")]
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
@@ -64,7 +65,6 @@ namespace osu.Game.Online.Chat
|
|||||||
public IBindableList<Channel> AvailableChannels => availableChannels;
|
public IBindableList<Channel> AvailableChannels => availableChannels;
|
||||||
|
|
||||||
private readonly IAPIProvider api;
|
private readonly IAPIProvider api;
|
||||||
private readonly IChatClient chatClient;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache users { get; set; }
|
private UserLookupCache users { get; set; }
|
||||||
@@ -72,6 +72,7 @@ namespace osu.Game.Online.Chat
|
|||||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||||
private ScheduledDelegate scheduledAck;
|
private ScheduledDelegate scheduledAck;
|
||||||
|
|
||||||
|
private IChatClient chatClient = null!;
|
||||||
private long? lastSilenceMessageId;
|
private long? lastSilenceMessageId;
|
||||||
private uint? lastSilenceId;
|
private uint? lastSilenceId;
|
||||||
|
|
||||||
@@ -79,14 +80,13 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
this.api = api;
|
this.api = api;
|
||||||
|
|
||||||
chatClient = api.GetChatClient();
|
|
||||||
|
|
||||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
chatClient = api.GetChatClient();
|
||||||
chatClient.ChannelJoined += ch => Schedule(() => joinChannel(ch));
|
chatClient.ChannelJoined += ch => Schedule(() => joinChannel(ch));
|
||||||
chatClient.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false));
|
chatClient.ChannelParted += ch => Schedule(() => leaveChannel(getChannel(ch), false));
|
||||||
chatClient.NewMessages += msgs => Schedule(() => addMessages(msgs));
|
chatClient.NewMessages += msgs => Schedule(() => addMessages(msgs));
|
||||||
@@ -282,8 +282,7 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
// Check if the user has joined the requested channel already.
|
// Check if the user has joined the requested channel already.
|
||||||
// This uses the channel name for comparison as the PM user's username is unavailable after a restart.
|
// This uses the channel name for comparison as the PM user's username is unavailable after a restart.
|
||||||
var privateChannel = JoinedChannels.FirstOrDefault(
|
var privateChannel = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase));
|
||||||
c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Name.Equals(content, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
if (privateChannel != null)
|
if (privateChannel != null)
|
||||||
{
|
{
|
||||||
@@ -645,7 +644,9 @@ namespace osu.Game.Online.Chat
|
|||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
chatClient?.Dispose();
|
|
||||||
|
if (chatClient.IsNotNull())
|
||||||
|
chatClient.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
@@ -35,6 +37,8 @@ namespace osu.Game.Online
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; } = null!;
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly IBindable<APIUser> localUser = new Bindable<APIUser>();
|
||||||
|
|
||||||
private readonly Dictionary<string, UserStatistics> statisticsCache = new Dictionary<string, UserStatistics>();
|
private readonly Dictionary<string, UserStatistics> statisticsCache = new Dictionary<string, UserStatistics>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -48,7 +52,8 @@ namespace osu.Game.Online
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
api.LocalUser.BindValueChanged(_ =>
|
localUser.BindTo(api.LocalUser);
|
||||||
|
localUser.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
// queuing up requests directly on user change is unsafe, as the API status may have not been updated yet.
|
// queuing up requests directly on user change is unsafe, as the API status may have not been updated yet.
|
||||||
// schedule a frame to allow the API to be in its correct state sending requests.
|
// schedule a frame to allow the API to be in its correct state sending requests.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
|
|
||||||
namespace osu.Game.Online.Metadata
|
namespace osu.Game.Online.Metadata
|
||||||
@@ -116,7 +117,7 @@ namespace osu.Game.Online.Metadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsWatchingUserPresence)
|
if (IsWatchingUserPresence)
|
||||||
BeginWatchingUserPresenceInternal();
|
BeginWatchingUserPresenceInternal().FireAndForget();
|
||||||
|
|
||||||
if (localUser.Value is not GuestUser)
|
if (localUser.Value is not GuestUser)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ namespace osu.Game.Online
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
if (notificationsClient.IsNotNull())
|
if (notificationsClient.IsNotNull())
|
||||||
notificationsClient.MessageReceived += notifyAboutForcedDisconnection;
|
notificationsClient.MessageReceived -= notifyAboutForcedDisconnection;
|
||||||
|
|
||||||
if (spectatorClient.IsNotNull())
|
if (spectatorClient.IsNotNull())
|
||||||
spectatorClient.Disconnecting -= notifyAboutForcedDisconnection;
|
spectatorClient.Disconnecting -= notifyAboutForcedDisconnection;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
[LocalisableDescription(typeof(MatchesStrings), nameof(MatchesStrings.MatchTeamTypesHeadToHead))]
|
[LocalisableDescription(typeof(MatchesStrings), nameof(MatchesStrings.MatchTeamTypesHeadToHead))]
|
||||||
HeadToHead,
|
HeadToHead,
|
||||||
|
|
||||||
[LocalisableDescription(typeof(MatchesStrings), nameof(MatchesStrings.MatchTeamTypesTeamVs))]
|
[LocalisableDescription(typeof(MatchesStrings), nameof(MatchesStrings.MatchTeamTypesTeamVersus))]
|
||||||
TeamVersus,
|
TeamVersus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
@@ -113,6 +112,9 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const float SCREEN_EDGE_MARGIN = 12f;
|
public const float SCREEN_EDGE_MARGIN = 12f;
|
||||||
|
|
||||||
|
private const double general_log_debounce = 60000;
|
||||||
|
private const string tablet_log_prefix = @"[Tablet] ";
|
||||||
|
|
||||||
public Toolbar Toolbar { get; private set; }
|
public Toolbar Toolbar { get; private set; }
|
||||||
|
|
||||||
private ChatOverlay chatOverlay;
|
private ChatOverlay chatOverlay;
|
||||||
@@ -241,12 +243,26 @@ namespace osu.Game
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool HideUnlicensedContent => false;
|
public virtual bool HideUnlicensedContent => false;
|
||||||
|
|
||||||
|
private bool tabletLogNotifyOnWarning = true;
|
||||||
|
private bool tabletLogNotifyOnError = true;
|
||||||
|
private int generalLogRecentCount;
|
||||||
|
|
||||||
public OsuGame(string[] args = null)
|
public OsuGame(string[] args = null)
|
||||||
{
|
{
|
||||||
this.args = args;
|
this.args = args;
|
||||||
|
|
||||||
forwardGeneralLogsToNotifications();
|
Logger.NewEntry += forwardGeneralLogToNotifications;
|
||||||
forwardTabletLogsToNotifications();
|
Logger.NewEntry += forwardTabletLogToNotifications;
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
||||||
|
tablet?.Tablet.BindValueChanged(_ =>
|
||||||
|
{
|
||||||
|
tabletLogNotifyOnWarning = true;
|
||||||
|
tabletLogNotifyOnError = true;
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IOverlayManager
|
#region IOverlayManager
|
||||||
@@ -347,40 +363,42 @@ namespace osu.Game
|
|||||||
if (host.Window != null)
|
if (host.Window != null)
|
||||||
{
|
{
|
||||||
host.Window.CursorState |= CursorState.Hidden;
|
host.Window.CursorState |= CursorState.Hidden;
|
||||||
host.Window.DragDrop += path =>
|
host.Window.DragDrop += onWindowDragDrop;
|
||||||
{
|
|
||||||
// on macOS/iOS, URL associations are handled via SDL_DROPFILE events.
|
|
||||||
if (path.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
HandleLink(path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (dragDropFiles)
|
|
||||||
{
|
|
||||||
dragDropFiles.Add(path);
|
|
||||||
|
|
||||||
Logger.Log($@"Adding ""{Path.GetFileName(path)}"" for import");
|
|
||||||
|
|
||||||
// File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
|
|
||||||
// In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
|
|
||||||
dragDropImportSchedule?.Cancel();
|
|
||||||
dragDropImportSchedule = Scheduler.AddDelayed(handlePendingDragDropImports, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePendingDragDropImports()
|
private void onWindowDragDrop(string path)
|
||||||
{
|
{
|
||||||
|
// on macOS/iOS, URL associations are handled via SDL_DROPFILE events.
|
||||||
|
if (path.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
HandleLink(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (dragDropFiles)
|
lock (dragDropFiles)
|
||||||
{
|
{
|
||||||
Logger.Log($"Handling batch import of {dragDropFiles.Count} files");
|
dragDropFiles.Add(path);
|
||||||
|
|
||||||
string[] paths = dragDropFiles.ToArray();
|
Logger.Log($@"Adding ""{Path.GetFileName(path)}"" for import");
|
||||||
dragDropFiles.Clear();
|
|
||||||
|
|
||||||
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
|
// File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
|
||||||
|
// In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
|
||||||
|
dragDropImportSchedule?.Cancel();
|
||||||
|
dragDropImportSchedule = Scheduler.AddDelayed(handlePendingDragDropImports, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePendingDragDropImports()
|
||||||
|
{
|
||||||
|
lock (dragDropFiles)
|
||||||
|
{
|
||||||
|
Logger.Log($"Handling batch import of {dragDropFiles.Count} files");
|
||||||
|
|
||||||
|
string[] paths = dragDropFiles.ToArray();
|
||||||
|
dragDropFiles.Clear();
|
||||||
|
|
||||||
|
Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,7 +1027,14 @@ namespace osu.Game
|
|||||||
detachedBeatmapStore?.Dispose();
|
detachedBeatmapStore?.Dispose();
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
SentryLogger.Dispose();
|
SentryLogger.Dispose();
|
||||||
|
|
||||||
|
if (Host?.Window != null)
|
||||||
|
Host.Window.DragDrop -= onWindowDragDrop;
|
||||||
|
|
||||||
|
Logger.NewEntry -= forwardGeneralLogToNotifications;
|
||||||
|
Logger.NewEntry -= forwardTabletLogToNotifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
||||||
@@ -1029,13 +1054,6 @@ namespace osu.Game
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
if (RuntimeInfo.EntryAssembly.GetCustomAttribute<OfficialBuildAttribute>() == null)
|
|
||||||
Logger.Log(NotificationsStrings.NotOfficialBuild.ToString());
|
|
||||||
|
|
||||||
// Make sure the release stream setting matches the build which was just run.
|
|
||||||
if (Enum.TryParse<ReleaseStream>(Version.Split('-').Last(), true, out var releaseStream))
|
|
||||||
LocalConfig.SetValue(OsuSetting.ReleaseStream, releaseStream);
|
|
||||||
|
|
||||||
var languages = Enum.GetValues<Language>();
|
var languages = Enum.GetValues<Language>();
|
||||||
|
|
||||||
var mappings = languages.Select(language =>
|
var mappings = languages.Select(language =>
|
||||||
@@ -1365,115 +1383,90 @@ namespace osu.Game
|
|||||||
overlay.Depth = (float)-Clock.CurrentTime;
|
overlay.Depth = (float)-Clock.CurrentTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forwardGeneralLogsToNotifications()
|
private void forwardGeneralLogToNotifications(LogEntry entry)
|
||||||
{
|
{
|
||||||
int recentLogCount = 0;
|
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return;
|
||||||
|
|
||||||
const double debounce = 60000;
|
if (entry.Exception is SentryOnlyDiagnosticsException)
|
||||||
|
return;
|
||||||
|
|
||||||
Logger.NewEntry += entry =>
|
const int short_term_display_limit = 3;
|
||||||
|
|
||||||
|
if (generalLogRecentCount < short_term_display_limit)
|
||||||
{
|
{
|
||||||
if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return;
|
Schedule(() => Notifications.Post(new SimpleErrorNotification
|
||||||
|
|
||||||
if (entry.Exception is SentryOnlyDiagnosticsException)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const int short_term_display_limit = 3;
|
|
||||||
|
|
||||||
if (recentLogCount < short_term_display_limit)
|
|
||||||
{
|
{
|
||||||
Schedule(() => Notifications.Post(new SimpleErrorNotification
|
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
|
||||||
{
|
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
|
||||||
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
|
}));
|
||||||
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
|
}
|
||||||
}));
|
else if (generalLogRecentCount == short_term_display_limit)
|
||||||
}
|
{
|
||||||
else if (recentLogCount == short_term_display_limit)
|
string logFile = Logger.GetLogger(entry.Target.Value).Filename;
|
||||||
|
|
||||||
|
Schedule(() => Notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
string logFile = Logger.GetLogger(entry.Target.Value).Filename;
|
Icon = FontAwesome.Solid.EllipsisH,
|
||||||
|
Text = NotificationsStrings.SubsequentMessagesLogged,
|
||||||
Schedule(() => Notifications.Post(new SimpleNotification
|
Activated = () =>
|
||||||
{
|
{
|
||||||
Icon = FontAwesome.Solid.EllipsisH,
|
Logger.Storage.PresentFileExternally(logFile);
|
||||||
Text = NotificationsStrings.SubsequentMessagesLogged,
|
return true;
|
||||||
Activated = () =>
|
}
|
||||||
{
|
}));
|
||||||
Logger.Storage.PresentFileExternally(logFile);
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
Interlocked.Increment(ref recentLogCount);
|
Interlocked.Increment(ref generalLogRecentCount);
|
||||||
Scheduler.AddDelayed(() => Interlocked.Decrement(ref recentLogCount), debounce);
|
Scheduler.AddDelayed(() => Interlocked.Decrement(ref generalLogRecentCount), general_log_debounce);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void forwardTabletLogsToNotifications()
|
private void forwardTabletLogToNotifications(LogEntry entry)
|
||||||
{
|
{
|
||||||
const string tablet_prefix = @"[Tablet] ";
|
if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_log_prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return;
|
||||||
|
|
||||||
bool notifyOnWarning = true;
|
string message = entry.Message.Replace(tablet_log_prefix, string.Empty);
|
||||||
bool notifyOnError = true;
|
|
||||||
|
|
||||||
Logger.NewEntry += entry =>
|
if (entry.Level == LogLevel.Error)
|
||||||
{
|
{
|
||||||
if (entry.Level < LogLevel.Important || entry.Target != LoggingTarget.Input || !entry.Message.StartsWith(tablet_prefix, StringComparison.OrdinalIgnoreCase))
|
if (!tabletLogNotifyOnError)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
string message = entry.Message.Replace(tablet_prefix, string.Empty);
|
tabletLogNotifyOnError = false;
|
||||||
|
|
||||||
if (entry.Level == LogLevel.Error)
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
if (!notifyOnError)
|
Notifications.Post(new SimpleNotification
|
||||||
return;
|
|
||||||
|
|
||||||
notifyOnError = false;
|
|
||||||
|
|
||||||
Schedule(() =>
|
|
||||||
{
|
{
|
||||||
Notifications.Post(new SimpleNotification
|
Text = NotificationsStrings.TabletSupportDisabledDueToError(message),
|
||||||
{
|
|
||||||
Text = NotificationsStrings.TabletSupportDisabledDueToError(message),
|
|
||||||
Icon = FontAwesome.Solid.PenSquare,
|
|
||||||
IconColour = Colours.RedDark,
|
|
||||||
});
|
|
||||||
|
|
||||||
// We only have one tablet handler currently.
|
|
||||||
// The loop here is weakly guarding against a future where more than one is added.
|
|
||||||
// If this is ever the case, this logic needs adjustment as it should probably only
|
|
||||||
// disable the relevant tablet handler rather than all.
|
|
||||||
foreach (var tabletHandler in Host.AvailableInputHandlers.OfType<ITabletHandler>())
|
|
||||||
tabletHandler.Enabled.Value = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (notifyOnWarning)
|
|
||||||
{
|
|
||||||
Schedule(() => Notifications.Post(new SimpleNotification
|
|
||||||
{
|
|
||||||
Text = NotificationsStrings.EncounteredTabletWarning,
|
|
||||||
Icon = FontAwesome.Solid.PenSquare,
|
Icon = FontAwesome.Solid.PenSquare,
|
||||||
IconColour = Colours.YellowDark,
|
IconColour = Colours.RedDark,
|
||||||
Activated = () =>
|
});
|
||||||
{
|
|
||||||
OpenUrlExternally("https://opentabletdriver.net/Tablets", LinkWarnMode.NeverWarn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
notifyOnWarning = false;
|
// We only have one tablet handler currently.
|
||||||
}
|
// The loop here is weakly guarding against a future where more than one is added.
|
||||||
};
|
// If this is ever the case, this logic needs adjustment as it should probably only
|
||||||
|
// disable the relevant tablet handler rather than all.
|
||||||
Schedule(() =>
|
foreach (var tabletHandler in Host.AvailableInputHandlers.OfType<ITabletHandler>())
|
||||||
|
tabletHandler.Enabled.Value = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (tabletLogNotifyOnWarning)
|
||||||
{
|
{
|
||||||
ITabletHandler tablet = Host.AvailableInputHandlers.OfType<ITabletHandler>().SingleOrDefault();
|
Schedule(() => Notifications.Post(new SimpleNotification
|
||||||
tablet?.Tablet.BindValueChanged(_ =>
|
|
||||||
{
|
{
|
||||||
notifyOnWarning = true;
|
Text = NotificationsStrings.EncounteredTabletWarning,
|
||||||
notifyOnError = true;
|
Icon = FontAwesome.Solid.PenSquare,
|
||||||
}, true);
|
IconColour = Colours.YellowDark,
|
||||||
});
|
Activated = () =>
|
||||||
|
{
|
||||||
|
OpenUrlExternally("https://opentabletdriver.net/Tablets", LinkWarnMode.NeverWarn);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
tabletLogNotifyOnWarning = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task asyncLoadStream;
|
private Task asyncLoadStream;
|
||||||
|
|||||||
@@ -504,9 +504,10 @@ namespace osu.Game
|
|||||||
AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic");
|
AddFont(Resources, @"Fonts/Inter/Inter-BoldItalic");
|
||||||
|
|
||||||
AddFont(Resources, @"Fonts/Noto/Noto-Basic");
|
AddFont(Resources, @"Fonts/Noto/Noto-Basic");
|
||||||
AddFont(Resources, @"Fonts/Noto/Noto-Hangul");
|
AddFont(Resources, @"Fonts/Noto/Noto-Bopomofo");
|
||||||
AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic");
|
AddFont(Resources, @"Fonts/Noto/Noto-CJK-Basic");
|
||||||
AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility");
|
AddFont(Resources, @"Fonts/Noto/Noto-CJK-Compatibility");
|
||||||
|
AddFont(Resources, @"Fonts/Noto/Noto-Hangul");
|
||||||
AddFont(Resources, @"Fonts/Noto/Noto-Thai");
|
AddFont(Resources, @"Fonts/Noto/Noto-Thai");
|
||||||
|
|
||||||
AddFont(Resources, @"Fonts/Venera/Venera-Light");
|
AddFont(Resources, @"Fonts/Venera/Venera-Light");
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
{
|
{
|
||||||
public partial class ChangelogBuild : FillFlowContainer
|
public partial class ChangelogBuild : FillFlowContainer
|
||||||
{
|
{
|
||||||
public Action<APIChangelogBuild> SelectBuild;
|
public required Action<APIChangelogBuild> SelectBuild { get; init; }
|
||||||
|
|
||||||
protected readonly APIChangelogBuild Build;
|
protected readonly APIChangelogBuild Build;
|
||||||
|
|
||||||
@@ -79,7 +77,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Action = () => SelectBuild?.Invoke(Build),
|
Action = () => SelectBuild.Invoke(Build),
|
||||||
Child = new FillFlowContainer<SpriteText>
|
Child = new FillFlowContainer<SpriteText>
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@@ -26,7 +24,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
{
|
{
|
||||||
public partial class ChangelogSingleBuild : ChangelogContent
|
public partial class ChangelogSingleBuild : ChangelogContent
|
||||||
{
|
{
|
||||||
private APIChangelogBuild build;
|
private readonly APIChangelogBuild build;
|
||||||
|
|
||||||
public ChangelogSingleBuild(APIChangelogBuild build)
|
public ChangelogSingleBuild(APIChangelogBuild build)
|
||||||
{
|
{
|
||||||
@@ -38,10 +36,12 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
{
|
{
|
||||||
bool complete = false;
|
bool complete = false;
|
||||||
|
|
||||||
|
APIChangelogBuild? onlineBuildDetails = null;
|
||||||
|
|
||||||
var req = new GetChangelogBuildRequest(build.UpdateStream.Name, build.Version);
|
var req = new GetChangelogBuildRequest(build.UpdateStream.Name, build.Version);
|
||||||
req.Success += res =>
|
req.Success += res =>
|
||||||
{
|
{
|
||||||
build = res;
|
onlineBuildDetails = res;
|
||||||
complete = true;
|
complete = true;
|
||||||
};
|
};
|
||||||
req.Failure += _ => complete = true;
|
req.Failure += _ => complete = true;
|
||||||
@@ -59,36 +59,35 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
Thread.Sleep(10);
|
Thread.Sleep(10);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build != null)
|
if (onlineBuildDetails == null) return;
|
||||||
|
|
||||||
|
CommentsContainer comments;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
CommentsContainer comments;
|
new ChangelogBuildWithNavigation(onlineBuildDetails) { SelectBuild = SelectBuild },
|
||||||
|
new Box
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild },
|
RelativeSizeAxes = Axes.X,
|
||||||
new Box
|
Height = 2,
|
||||||
{
|
Colour = colourProvider.Background6,
|
||||||
RelativeSizeAxes = Axes.X,
|
Margin = new MarginPadding { Top = 30 },
|
||||||
Height = 2,
|
},
|
||||||
Colour = colourProvider.Background6,
|
new ChangelogSupporterPromo
|
||||||
Margin = new MarginPadding { Top = 30 },
|
{
|
||||||
},
|
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
||||||
new ChangelogSupporterPromo
|
},
|
||||||
{
|
new Box
|
||||||
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
{
|
||||||
},
|
RelativeSizeAxes = Axes.X,
|
||||||
new Box
|
Height = 2,
|
||||||
{
|
Colour = colourProvider.Background6,
|
||||||
RelativeSizeAxes = Axes.X,
|
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
||||||
Height = 2,
|
},
|
||||||
Colour = colourProvider.Background6,
|
comments = new CommentsContainer()
|
||||||
Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
|
};
|
||||||
},
|
|
||||||
comments = new CommentsContainer()
|
|
||||||
};
|
|
||||||
|
|
||||||
comments.ShowComments(CommentableType.Build, build.Id);
|
comments.ShowComments(CommentableType.Build, onlineBuildDetails.Id);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ChangelogBuildWithNavigation : ChangelogBuild
|
public partial class ChangelogBuildWithNavigation : ChangelogBuild
|
||||||
@@ -98,7 +97,7 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuSpriteText date;
|
private OsuSpriteText date = null!;
|
||||||
|
|
||||||
protected override FillFlowContainer CreateHeader()
|
protected override FillFlowContainer CreateHeader()
|
||||||
{
|
{
|
||||||
@@ -144,9 +143,9 @@ namespace osu.Game.Overlays.Changelog
|
|||||||
|
|
||||||
private partial class NavigationIconButton : IconButton
|
private partial class NavigationIconButton : IconButton
|
||||||
{
|
{
|
||||||
public Action<APIChangelogBuild> SelectBuild;
|
public required Action<APIChangelogBuild> SelectBuild { get; init; }
|
||||||
|
|
||||||
public NavigationIconButton(APIChangelogBuild build)
|
public NavigationIconButton(APIChangelogBuild? build)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Development;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@@ -22,7 +19,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.Behaviour))]
|
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.Behaviour))]
|
||||||
public partial class ScreenBehaviour : WizardScreen
|
public partial class ScreenBehaviour : WizardScreen
|
||||||
{
|
{
|
||||||
private SearchContainer<SettingsSection> searchContainer;
|
private SearchContainer<SettingsSection> searchContainer = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
@@ -91,13 +88,11 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
new GraphicsSection(),
|
new GraphicsSection(),
|
||||||
new OnlineSection(),
|
new OnlineSection(),
|
||||||
new MaintenanceSection(),
|
new MaintenanceSection(),
|
||||||
|
new DebugSection()
|
||||||
},
|
},
|
||||||
SearchTerm = SettingsItem<bool>.CLASSIC_DEFAULT_SEARCH_TERM,
|
SearchTerm = SettingsItem<bool>.CLASSIC_DEFAULT_SEARCH_TERM,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DebugUtils.IsDebugBuild)
|
|
||||||
searchContainer.Add(new DebugSection());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyClassic()
|
private void applyClassic()
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
mod.ApplyToDifficulty(adjustedDifficulty);
|
mod.ApplyToDifficulty(adjustedDifficulty);
|
||||||
|
|
||||||
Ruleset ruleset = GameRuleset.Value.CreateInstance();
|
Ruleset ruleset = GameRuleset.Value.CreateInstance();
|
||||||
adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(adjustedDifficulty, rate);
|
adjustedDifficulty = ruleset.GetAdjustedDisplayDifficulty(adjustedDifficulty, Mods.Value);
|
||||||
|
|
||||||
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
|
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@@ -20,12 +21,13 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
|
|
||||||
public DebugSection()
|
public DebugSection()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
if (DebugUtils.IsDebugBuild)
|
||||||
{
|
{
|
||||||
new GeneralSettings(),
|
Add(new GeneralSettings());
|
||||||
new BatchImportSettings(),
|
Add(new BatchImportSettings());
|
||||||
new MemorySettings(),
|
}
|
||||||
};
|
|
||||||
|
Add(new MemorySettings());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
||||||
@@ -15,19 +14,17 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig)
|
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig)
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Add(new SettingsCheckbox
|
||||||
{
|
{
|
||||||
new SettingsCheckbox
|
LabelText = @"Show log overlay",
|
||||||
{
|
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
|
||||||
LabelText = @"Show log overlay",
|
});
|
||||||
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay)
|
|
||||||
},
|
Add(new SettingsCheckbox
|
||||||
new SettingsCheckbox
|
{
|
||||||
{
|
LabelText = @"Bypass front-to-back render pass",
|
||||||
LabelText = @"Bypass front-to-back render pass",
|
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
|
||||||
Current = config.GetBindable<bool>(DebugSetting.BypassFrontToBackPass)
|
});
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@@ -24,73 +26,112 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings
|
|||||||
SettingsButton blockAction;
|
SettingsButton blockAction;
|
||||||
SettingsButton unblockAction;
|
SettingsButton unblockAction;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Add(new SettingsButton
|
||||||
{
|
{
|
||||||
new SettingsButton
|
Text = @"Clear all caches",
|
||||||
|
Action = () =>
|
||||||
{
|
{
|
||||||
Text = @"Clear all caches",
|
host.Collect();
|
||||||
Action = host.Collect
|
|
||||||
},
|
// host.Collect() uses GCCollectionMode.Optimized, but we should be as aggressive as possible here.
|
||||||
new SettingsButton
|
GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
SettingsEnumDropdown<GCLatencyMode> latencyModeDropdown;
|
||||||
|
Add(latencyModeDropdown = new SettingsEnumDropdown<GCLatencyMode>
|
||||||
|
{
|
||||||
|
LabelText = "GC mode",
|
||||||
|
});
|
||||||
|
|
||||||
|
latencyModeDropdown.Current.BindValueChanged(mode =>
|
||||||
|
{
|
||||||
|
Logger.Log($"Changing latency mode: {mode.NewValue}");
|
||||||
|
|
||||||
|
switch (mode.NewValue)
|
||||||
{
|
{
|
||||||
Text = @"Compact realm",
|
case GCLatencyMode.Default:
|
||||||
Action = () =>
|
// https://github.com/ppy/osu-framework/blob/1d5301018dfed1a28702be56e1d53c4835b199f2/osu.Framework/Platform/GameHost.cs#L703
|
||||||
|
GCSettings.LatencyMode = System.Runtime.GCLatencyMode.SustainedLowLatency;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GCLatencyMode.Interactive:
|
||||||
|
GCSettings.LatencyMode = System.Runtime.GCLatencyMode.Interactive;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (DebugUtils.IsDebugBuild)
|
||||||
|
{
|
||||||
|
AddRange(new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsButton
|
||||||
{
|
{
|
||||||
// Blocking operations implicitly causes a Compact().
|
Text = @"Compact realm",
|
||||||
using (realm.BlockAllOperations(@"compact"))
|
Action = () =>
|
||||||
{
|
{
|
||||||
|
// Blocking operations implicitly causes a Compact().
|
||||||
|
using (realm.BlockAllOperations(@"compact"))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
blockAction = new SettingsButton
|
||||||
|
{
|
||||||
|
Text = @"Block realm",
|
||||||
|
},
|
||||||
|
unblockAction = new SettingsButton
|
||||||
|
{
|
||||||
|
Text = @"Unblock realm",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
blockAction.Action = () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IDisposable? token = realm.BlockAllOperations(@"maintenance");
|
||||||
|
|
||||||
|
blockAction.Enabled.Value = false;
|
||||||
|
|
||||||
|
// As a safety measure, unblock after 10 seconds.
|
||||||
|
// This is to handle the case where a dev may block, but then something on the update thread
|
||||||
|
// accesses realm and blocks for eternity.
|
||||||
|
Task.Factory.StartNew(() =>
|
||||||
|
{
|
||||||
|
Thread.Sleep(10000);
|
||||||
|
unblock();
|
||||||
|
});
|
||||||
|
|
||||||
|
unblockAction.Action = unblock;
|
||||||
|
|
||||||
|
void unblock()
|
||||||
|
{
|
||||||
|
if (token.IsNull())
|
||||||
|
return;
|
||||||
|
|
||||||
|
token.Dispose();
|
||||||
|
token = null;
|
||||||
|
|
||||||
|
Scheduler.Add(() =>
|
||||||
|
{
|
||||||
|
blockAction.Enabled.Value = true;
|
||||||
|
unblockAction.Action = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
catch (Exception e)
|
||||||
blockAction = new SettingsButton
|
|
||||||
{
|
|
||||||
Text = @"Block realm",
|
|
||||||
},
|
|
||||||
unblockAction = new SettingsButton
|
|
||||||
{
|
|
||||||
Text = @"Unblock realm",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
blockAction.Action = () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IDisposable? token = realm.BlockAllOperations(@"maintenance");
|
|
||||||
|
|
||||||
blockAction.Enabled.Value = false;
|
|
||||||
|
|
||||||
// As a safety measure, unblock after 10 seconds.
|
|
||||||
// This is to handle the case where a dev may block, but then something on the update thread
|
|
||||||
// accesses realm and blocks for eternity.
|
|
||||||
Task.Factory.StartNew(() =>
|
|
||||||
{
|
{
|
||||||
Thread.Sleep(10000);
|
Logger.Error(e, @"Blocking realm failed");
|
||||||
unblock();
|
|
||||||
});
|
|
||||||
|
|
||||||
unblockAction.Action = unblock;
|
|
||||||
|
|
||||||
void unblock()
|
|
||||||
{
|
|
||||||
if (token.IsNull())
|
|
||||||
return;
|
|
||||||
|
|
||||||
token.Dispose();
|
|
||||||
token = null;
|
|
||||||
|
|
||||||
Scheduler.Add(() =>
|
|
||||||
{
|
|
||||||
blockAction.Enabled.Value = true;
|
|
||||||
unblockAction.Action = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
catch (Exception e)
|
}
|
||||||
{
|
}
|
||||||
Logger.Error(e, @"Blocking realm failed");
|
|
||||||
}
|
private enum GCLatencyMode
|
||||||
};
|
{
|
||||||
|
Default,
|
||||||
|
Interactive,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,11 +313,11 @@ namespace osu.Game.Overlays.Settings.Sections
|
|||||||
base.PopIn();
|
base.PopIn();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rename() => skins.CurrentSkinInfo.Value.PerformWrite(skin =>
|
private void rename()
|
||||||
{
|
{
|
||||||
skin.Name = textBox.Text;
|
skins.Rename(skins.CurrentSkinInfo.Value, textBox.Text);
|
||||||
PopOut();
|
PopOut();
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Development;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@@ -31,7 +30,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
protected override IEnumerable<SettingsSection> CreateSections()
|
protected override IEnumerable<SettingsSection> CreateSections()
|
||||||
{
|
{
|
||||||
var sections = new List<SettingsSection>
|
return new List<SettingsSection>
|
||||||
{
|
{
|
||||||
// This list should be kept in sync with ScreenBehaviour.
|
// This list should be kept in sync with ScreenBehaviour.
|
||||||
new GeneralSection(),
|
new GeneralSection(),
|
||||||
@@ -44,12 +43,8 @@ namespace osu.Game.Overlays
|
|||||||
new GraphicsSection(),
|
new GraphicsSection(),
|
||||||
new OnlineSection(),
|
new OnlineSection(),
|
||||||
new MaintenanceSection(),
|
new MaintenanceSection(),
|
||||||
|
new DebugSection()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (DebugUtils.IsDebugBuild)
|
|
||||||
sections.Add(new DebugSection());
|
|
||||||
|
|
||||||
return sections;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<SettingsSubPanel> subPanels = new List<SettingsSubPanel>();
|
private readonly List<SettingsSubPanel> subPanels = new List<SettingsSubPanel>();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user