mirror of
https://github.com/ppy/osu.git
synced 2026-05-13 23:23:32 +08:00
Compare commits
247 Commits
2026.209.0
...
pp-dev
@@ -1,19 +0,0 @@
|
||||
name: Copy labels from linked issues
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write # to read the labels of any linked issue(s), and to put the found labels if any on the PR
|
||||
# not granting any `pull_requests` permissions because in github's modeling pull requests are a subset of issues. it's confusing.
|
||||
|
||||
jobs:
|
||||
copy-labels:
|
||||
runs-on: ubuntu-latest
|
||||
name: Copy labels from linked issues
|
||||
steps:
|
||||
- name: Copy labels
|
||||
uses: michalvankodev/copy-issue-labels@v1.3.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -38,8 +38,12 @@ jobs:
|
||||
run: ./UseLocalOsu.sh
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Build tools
|
||||
run: dotnet build PerformanceCalculator --nologo --verbosity quiet
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Regenerate mod definitions
|
||||
run: dotnet run --project PerformanceCalculator -- mods > ../osu-web/database/mods.json
|
||||
run: dotnet run --project PerformanceCalculator --no-build -- mods > ../osu-web/database/mods.json
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
|
||||
|
||||
+3
-3
@@ -19,13 +19,13 @@ namespace osu.Game.Rulesets.EmptyFreeform
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||
|
||||
+3
-3
@@ -19,13 +19,13 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
|
||||
|
||||
+3
-3
@@ -19,13 +19,13 @@ namespace osu.Game.Rulesets.EmptyScrolling
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -10,8 +10,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
|
||||
|
||||
+3
-3
@@ -19,13 +19,13 @@ namespace osu.Game.Rulesets.Pippidon
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
return new DifficultyAttributes(mods, 0);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty<DifficultyHitObject>();
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods) => Enumerable.Empty<DifficultyHitObject>();
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => Array.Empty<Skill>();
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => Array.Empty<Skill>();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.129.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.310.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -5,16 +5,13 @@
|
||||
android:supportsRtl="true"
|
||||
android:label="osu!"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher" />
|
||||
<!-- for editor usage -->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<!--
|
||||
READ_MEDIA_* permissions are available only on API 33 or greater. Devices with older android versions
|
||||
don't understand the new permissions, so request the old READ_EXTERNAL_STORAGE permission to get storage access.
|
||||
Since the old permission has no effect on >= API 33, don't request it.
|
||||
|
||||
Care needs to be taken to ensure runtime permission checks target the correct permission for the API level.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
|
||||
android:roundIcon="@mipmap/ic_launcher">
|
||||
<provider android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="sh.ppy.osulazer.fileprovider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<!-- https://developer.android.com/reference/androidx/core/content/FileProvider -->
|
||||
<external-files-path path="logs" name="logs" />
|
||||
<external-files-path path="exports" name="exports" />
|
||||
</paths>
|
||||
@@ -24,8 +24,8 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="System.IO.Packaging" Version="10.0.5" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.6.1.70" />
|
||||
<PackageReference Include="Velopack" Version="0.0.1298" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
|
||||
@@ -5,7 +5,7 @@ using BenchmarkDotNet.Attributes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osu.Game.Tests.NonVisual.Filtering;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Benchmarks
|
||||
Status = BeatmapOnlineStatus.Loved
|
||||
};
|
||||
|
||||
private CarouselBeatmap carouselBeatmap = null!;
|
||||
private FilterMatchingTest.CarouselBeatmap carouselBeatmap = null!;
|
||||
private FilterCriteria criteria1 = null!;
|
||||
private FilterCriteria criteria2 = null!;
|
||||
private FilterCriteria criteria3 = null!;
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Benchmarks
|
||||
var beatmap = getExampleBeatmap();
|
||||
beatmap.OnlineID = 20201010;
|
||||
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
|
||||
carouselBeatmap = new CarouselBeatmap(beatmap);
|
||||
carouselBeatmap = new FilterMatchingTest.CarouselBeatmap(beatmap);
|
||||
criteria1 = new FilterCriteria();
|
||||
criteria2 = new FilterCriteria
|
||||
{
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageReference Include="nunit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.15.8" />
|
||||
<PackageReference Include="nunit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Catch.Skinning.Legacy;
|
||||
@@ -21,9 +22,9 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
var skinSource = new SkinProvidingContainer(rawSkin);
|
||||
var skin = new CatchLegacySkinTransformer(skinSource);
|
||||
|
||||
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value);
|
||||
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value);
|
||||
Assert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value);
|
||||
ClassicAssert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value);
|
||||
ClassicAssert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value);
|
||||
ClassicAssert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value);
|
||||
}
|
||||
|
||||
private class TestLegacySkin : LegacySkin
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -176,15 +176,20 @@ namespace osu.Game.Rulesets.Catch
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.SmallTickHit,
|
||||
HitResult.SmallTickMiss,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
@@ -22,8 +23,6 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
private const double difficulty_multiplier = 4.59;
|
||||
|
||||
private float halfCatcherWidth;
|
||||
|
||||
public override int Version => 20251020;
|
||||
|
||||
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
@@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new CatchDifficultyAttributes { Mods = mods };
|
||||
@@ -46,12 +45,19 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
return attributes;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
CatchHitObject? lastObject = null;
|
||||
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
double clockRate = ModUtils.CalculateRateWithMods(mods);
|
||||
|
||||
float halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
|
||||
|
||||
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
|
||||
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);
|
||||
|
||||
// In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream.
|
||||
foreach (var hitObject in CatchBeatmap.GetPalpableObjects(beatmap.HitObjects))
|
||||
{
|
||||
@@ -68,16 +74,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
return objects;
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
|
||||
|
||||
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
|
||||
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);
|
||||
|
||||
return new Skill[]
|
||||
{
|
||||
new Movement(mods, halfCatcherWidth, clockRate),
|
||||
new Movement(mods),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -11,12 +11,16 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Evaluators
|
||||
{
|
||||
private const double direction_change_bonus = 21.0;
|
||||
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, double catcherSpeedMultiplier)
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
var catchCurrent = (CatchDifficultyHitObject)current;
|
||||
var catchLast = (CatchDifficultyHitObject)current.Previous(0);
|
||||
var catchLastLast = (CatchDifficultyHitObject)current.Previous(1);
|
||||
|
||||
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
|
||||
// but also the speed of the player's catcher, which has an impact on difficulty
|
||||
double catcherSpeedMultiplier = current.ClockRate;
|
||||
|
||||
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catcherSpeedMultiplier);
|
||||
|
||||
double distanceAddition = (Math.Pow(Math.Abs(catchCurrent.DistanceMoved), 1.3) / 510);
|
||||
@@ -40,6 +44,30 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Evaluators
|
||||
/ (CatchDifficultyHitObject.NORMALIZED_HALF_CATCHER_WIDTH * 6) / sqrtStrain;
|
||||
}
|
||||
|
||||
// Linear spacing nerf.
|
||||
double linearSpacingCount = 0;
|
||||
|
||||
for (int i = 0; i < Math.Min(current.Index, 10); i++)
|
||||
{
|
||||
var catchPrevObj = (CatchDifficultyHitObject)catchCurrent.Previous(i);
|
||||
|
||||
// Only same direction movements matter as they do not take any additional inputs.
|
||||
if (Math.Sign(catchCurrent.DistanceMoved) != Math.Sign(catchPrevObj.DistanceMoved) || catchCurrent.DistanceMoved == 0 || catchPrevObj.DistanceMoved == 0)
|
||||
break;
|
||||
|
||||
double currentSpacing = Math.Abs(catchCurrent.DistanceMoved / catchCurrent.StrainTime);
|
||||
double prevSpacing = Math.Abs(catchPrevObj.DistanceMoved / catchPrevObj.StrainTime);
|
||||
|
||||
double relativeDifference = Math.Abs(currentSpacing / prevSpacing - 1);
|
||||
|
||||
if (relativeDifference > 0.05)
|
||||
break;
|
||||
|
||||
linearSpacingCount++;
|
||||
}
|
||||
|
||||
distanceAddition *= Math.Pow(0.7, linearSpacingCount);
|
||||
|
||||
// Bonus for edge dashes.
|
||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f)
|
||||
{
|
||||
|
||||
@@ -17,28 +17,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||
|
||||
protected override int SectionLength => 750;
|
||||
|
||||
protected readonly float HalfCatcherWidth;
|
||||
|
||||
/// <summary>
|
||||
/// The speed multiplier applied to the player's catcher.
|
||||
/// </summary>
|
||||
private readonly double catcherSpeedMultiplier;
|
||||
|
||||
public Movement(Mod[] mods, float halfCatcherWidth, double clockRate)
|
||||
public Movement(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
HalfCatcherWidth = halfCatcherWidth;
|
||||
|
||||
// In catch, clockrate adjustments do not only affect the timings of hitobjects,
|
||||
// but also the speed of the player's catcher, which has an impact on difficulty
|
||||
// TODO: Support variable clockrates caused by mods such as ModTimeRamp
|
||||
// (perhaps by using IApplicableToRate within the CatchDifficultyHitObject constructor to set a catcher speed for each object before processing)
|
||||
catcherSpeedMultiplier = clockRate;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
return MovementEvaluator.EvaluateDifficultyOf(current, catcherSpeedMultiplier);
|
||||
return MovementEvaluator.EvaluateDifficultyOf(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using DragArea = osu.Game.Screens.Edit.Compose.Components.Timeline.TimelineHitObjectBlueprint.DragArea;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneHoldNoteTailDrag : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
AddStep("Clear objects", () => EditorBeatmap.Clear());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimpleTailDragForward()
|
||||
{
|
||||
AddStep("Add hold note", () =>
|
||||
{
|
||||
EditorBeatmap.Add(new HoldNote { StartTime = 2170, Duration = 937.5 });
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().Single();
|
||||
dragForward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is higher", () => ((HoldNote)EditorBeatmap.HitObjects.First())!.Duration > 937.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimpleTailDragBackwards()
|
||||
{
|
||||
AddStep("Add hold note", () =>
|
||||
{
|
||||
EditorBeatmap.Add(new HoldNote { StartTime = 2170, Duration = 937.5 });
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().Single();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is lower", () => ((HoldNote)EditorBeatmap.HitObjects[0]).Duration < 937.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePositionButNotSelectedDragForward()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragForward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is higher, other is unchanged", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration > 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration == 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePositionButNotSelectedDragBackward()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is lower, other is unchanged", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration < 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration == 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePositionSelectedDragForward()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragForward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Both durations are higher", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration > 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration > 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSamePositionSelectedDragBackward()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Both durations are lower", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration < 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration < 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectedButDifferentPositions()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2404, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is unchanged, other is lower", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration == 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration < 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectedSameStartTimeDifferentDurations()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 1171.8, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag until both match", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
InputManager.MoveMouseTo(blueprintDragArea);
|
||||
InputManager.PressKey(Key.LShift);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
InputManager.MoveMouseTo(new Vector2(1000, 110));
|
||||
});
|
||||
|
||||
AddStep("Continue the drag", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is unchanged, other is lower", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration == 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration < 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectedSameDurationDifferentStartTimes()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2638.7, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is unchanged, other is lower", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration == 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration < 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragNoteOutsideOfSelection()
|
||||
{
|
||||
AddStep("Add hold notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 1 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select the back stack slider", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.Last());
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Duration is lower, other is unchanged", () =>
|
||||
((HoldNote)EditorBeatmap.HitObjects[0]).Duration < 937.5f &&
|
||||
((HoldNote)EditorBeatmap.HitObjects[^1]).Duration == 937.5f
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragHoldNoteWithNotes()
|
||||
{
|
||||
AddStep("Add notes", () =>
|
||||
{
|
||||
EditorBeatmap.AddRange([
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 0 },
|
||||
new Note { StartTime = 2170, Column = 1 },
|
||||
new Note { StartTime = 3107.5, Column = 2 },
|
||||
new HoldNote { StartTime = 2170, Duration = 937.5, Column = 3 }
|
||||
]);
|
||||
});
|
||||
|
||||
AddStep("Select all", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||
});
|
||||
|
||||
AddStep("Drag tail", () =>
|
||||
{
|
||||
var blueprintDragArea = this.ChildrenOfType<DragArea>().First();
|
||||
dragBackward(blueprintDragArea);
|
||||
});
|
||||
|
||||
AddStep("Release tail", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("Both durations are lower", () =>
|
||||
{
|
||||
var holdNotes = EditorBeatmap.HitObjects.OfType<HoldNote>();
|
||||
return holdNotes.First().Duration < 937.5f && holdNotes.Last().Duration < 937.5f;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void dragForward(DragArea dragArea)
|
||||
{
|
||||
InputManager.MoveMouseTo(dragArea);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
InputManager.MoveMouseTo(new Vector2(1100, 110));
|
||||
}
|
||||
|
||||
private void dragBackward(DragArea dragArea)
|
||||
{
|
||||
InputManager.MoveMouseTo(dragArea);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
InputManager.MoveMouseTo(new Vector2(700, 110));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true));
|
||||
placeObject();
|
||||
AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking);
|
||||
AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime));
|
||||
AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime!));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
@@ -18,19 +19,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "1");
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
@@ -44,19 +45,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "1,3,5,7");
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
@@ -70,19 +71,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "1");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
@@ -96,19 +97,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "1,3,5,7");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
@@ -122,23 +123,23 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 5 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
@@ -153,23 +154,23 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "7");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 5 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
ClassicAssert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 7 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
ClassicAssert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 9 }),
|
||||
new FilterCriteria()));
|
||||
}
|
||||
@@ -179,9 +180,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
|
||||
ClassicAssert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "some text"));
|
||||
ClassicAssert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
|
||||
ClassicAssert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
@@ -199,7 +200,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 0,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0");
|
||||
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -207,7 +208,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "100");
|
||||
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -215,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 100
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "1");
|
||||
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -223,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "0.1");
|
||||
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -231,7 +232,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 1000,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
@@ -249,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 0,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo1, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0");
|
||||
BeatmapInfo beatmapInfo2 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -257,7 +258,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 0
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo2, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "100");
|
||||
BeatmapInfo beatmapInfo3 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -265,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 100
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo3, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1");
|
||||
BeatmapInfo beatmapInfo4 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -273,7 +274,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo4, filterCriteria));
|
||||
|
||||
criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "0.1");
|
||||
BeatmapInfo beatmapInfo5 = new BeatmapInfo(new ManiaRuleset().RulesetInfo)
|
||||
@@ -281,7 +282,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 1000,
|
||||
EndTimeObjectCount = 1
|
||||
};
|
||||
Assert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
ClassicAssert.True(criteria.Matches(beatmapInfo5, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
@@ -299,7 +300,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
TotalObjectCount = 100,
|
||||
EndTimeObjectCount = 50
|
||||
};
|
||||
Assert.False(criteria.Matches(beatmapInfo, filterCriteria));
|
||||
ClassicAssert.False(criteria.Matches(beatmapInfo, filterCriteria));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
@@ -307,8 +308,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1some text"));
|
||||
ClassicAssert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.Equal, "some text"));
|
||||
ClassicAssert.False(criteria.TryParseCustomKeywordCriteria("lns", Operator.GreaterOrEqual, "1some text"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
@@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
var definition = new StageDefinition(columns);
|
||||
var results = getResults(definition);
|
||||
Assert.AreEqual(special, results);
|
||||
ClassicAssert.AreEqual(special, results);
|
||||
}
|
||||
|
||||
private IEnumerable<bool> getResults(StageDefinition definition)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
@@ -20,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
public void TestMapHasNoHoldNotes()
|
||||
{
|
||||
var testBeatmap = createModdedBeatmap();
|
||||
Assert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
|
||||
ClassicAssert.False(testBeatmap.HitObjects.OfType<HoldNote>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@@ -33,11 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
ClassicAssert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -54,11 +55,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
ClassicAssert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -74,11 +75,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
ClassicAssert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -96,13 +97,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 2, "Incorrect number of frames");
|
||||
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
|
||||
ClassicAssert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
|
||||
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -119,15 +120,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
||||
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
ClassicAssert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
|
||||
ClassicAssert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
|
||||
ClassicAssert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -146,16 +147,16 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
||||
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
||||
Assert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 4, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
ClassicAssert.AreEqual(3000, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
|
||||
ClassicAssert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
|
||||
ClassicAssert.AreEqual(4000, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -173,14 +174,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
var generated = new ManiaAutoGenerator(beatmap).Generate();
|
||||
|
||||
Assert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames");
|
||||
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
||||
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
|
||||
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
|
||||
ClassicAssert.AreEqual(generated.Frames.Count, frame_offset + 3, "Incorrect number of frames");
|
||||
ClassicAssert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
|
||||
ClassicAssert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
|
||||
ClassicAssert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
|
||||
ClassicAssert.True(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
|
||||
ClassicAssert.False(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
|
||||
}
|
||||
|
||||
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -19,6 +19,7 @@ using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
@@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.MatchesOnlineID(ruleset);
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new ManiaDifficultyAttributes { Mods = mods };
|
||||
@@ -62,11 +63,13 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||
int totalColumns = ((ManiaBeatmap)beatmap).TotalColumns;
|
||||
|
||||
double clockRate = ModUtils.CalculateRateWithMods(mods);
|
||||
|
||||
LegacySortHelper<HitObject>.Sort(sortedObjects, Comparer<HitObject>.Create((a, b) => (int)Math.Round(a.StartTime) - (int)Math.Round(b.StartTime)));
|
||||
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
@@ -88,7 +91,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
// Sorting is done in CreateDifficultyHitObjects, since the full list of hitobjects is required.
|
||||
protected override IEnumerable<DifficultyHitObject> SortObjects(IEnumerable<DifficultyHitObject> input) => input;
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[]
|
||||
{
|
||||
new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns)
|
||||
};
|
||||
|
||||
@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderDescending().First(v => variant >= v);
|
||||
}
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
@@ -392,9 +392,11 @@ namespace osu.Game.Rulesets.Mania
|
||||
HitResult.Good,
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
|
||||
// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as
|
||||
// it would be a bit redundant to show this to the user.
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.ComboBreak,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
@@ -30,6 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
PathType.LINEAR,
|
||||
new Vector2(100, 0),
|
||||
new Vector2(100, 100)
|
||||
),
|
||||
createPathSegment(
|
||||
PathType.PERFECT_CURVE,
|
||||
new Vector2(100.009f, -50.0009f),
|
||||
new Vector2(200.0089f, -100)
|
||||
),
|
||||
createPathSegment(
|
||||
PathType.PERFECT_CURVE,
|
||||
new Vector2(25, -50),
|
||||
new Vector2(100, 75)
|
||||
)
|
||||
};
|
||||
|
||||
@@ -48,9 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
[TestCase(0, 250)]
|
||||
[TestCase(0, 200)]
|
||||
[TestCase(1, 120)]
|
||||
[TestCase(1, 80)]
|
||||
public void TestSliderReversal(int pathIndex, double length)
|
||||
[TestCase(1, 120, false, false)]
|
||||
[TestCase(1, 80, false, false)]
|
||||
[TestCase(2, 250)]
|
||||
[TestCase(2, 190)]
|
||||
[TestCase(3, 250)]
|
||||
[TestCase(3, 190)]
|
||||
public void TestSliderReversal(int pathIndex, double length, bool assertEqualDistances = true, bool assertSliderReduction = true)
|
||||
{
|
||||
var controlPoints = paths[pathIndex];
|
||||
|
||||
@@ -90,6 +107,215 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
});
|
||||
|
||||
if (pathIndex == 2)
|
||||
{
|
||||
AddRepeatStep("Reverse slider again", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.Key(Key.G);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
}, 2);
|
||||
}
|
||||
|
||||
if (assertEqualDistances)
|
||||
{
|
||||
AddAssert("Middle control point has the same distance from start to end", () =>
|
||||
{
|
||||
var pathControlPoints = selectedSlider.Path.ControlPoints;
|
||||
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
|
||||
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
|
||||
|
||||
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
|
||||
});
|
||||
}
|
||||
|
||||
AddAssert("Middle control point is not at start or end", () =>
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
|
||||
);
|
||||
|
||||
AddAssert("Slider has correct length", () =>
|
||||
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
|
||||
|
||||
AddAssert("Slider has correct start position", () =>
|
||||
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);
|
||||
|
||||
AddAssert("Slider has correct end position", () =>
|
||||
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);
|
||||
|
||||
AddAssert("Control points have correct types", () =>
|
||||
{
|
||||
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();
|
||||
|
||||
return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
|
||||
});
|
||||
|
||||
if (assertSliderReduction)
|
||||
{
|
||||
AddStep("Move to marker", () =>
|
||||
{
|
||||
var marker = this.ChildrenOfType<SliderEndDragMarker>().Single();
|
||||
var markerPos = (marker.ScreenSpaceDrawQuad.TopRight + marker.ScreenSpaceDrawQuad.BottomRight) / 2;
|
||||
// sometimes the cursor may miss the marker's hitbox so we
|
||||
// add a little offset here to be sure it lands in a clickable position.
|
||||
var position = new Vector2(markerPos.X + 2f, markerPos.Y);
|
||||
InputManager.MoveMouseTo(position);
|
||||
});
|
||||
AddStep("Click", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("Reduce slider", () =>
|
||||
{
|
||||
var middleControlPoint = this.ChildrenOfType<PathControlPointPiece<Slider>>().ToArray()[^2];
|
||||
InputManager.MoveMouseTo(middleControlPoint);
|
||||
});
|
||||
AddStep("Release click", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddStep("Save half slider info", () =>
|
||||
{
|
||||
oldStartPos = selectedSlider.Position;
|
||||
oldEndPos = selectedSlider.EndPosition;
|
||||
oldDistance = selectedSlider.Path.Distance;
|
||||
});
|
||||
|
||||
AddStep("Reverse slider", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.Key(Key.G);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
});
|
||||
|
||||
AddAssert("Middle control point has the same distance from start to end", () =>
|
||||
{
|
||||
var pathControlPoints = selectedSlider.Path.ControlPoints;
|
||||
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
|
||||
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
|
||||
|
||||
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
|
||||
});
|
||||
|
||||
AddAssert("Middle control point is not at start or end", () =>
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
|
||||
);
|
||||
|
||||
AddAssert("Slider has correct length", () =>
|
||||
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
|
||||
|
||||
AddAssert("Slider has correct start position", () =>
|
||||
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);
|
||||
|
||||
AddAssert("Slider has correct end position", () =>
|
||||
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);
|
||||
|
||||
AddAssert("Control points have correct types", () =>
|
||||
{
|
||||
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();
|
||||
|
||||
return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSegmentedSliderReversal()
|
||||
{
|
||||
PathControlPoint[] segmentedSliderPath =
|
||||
[
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(0, 0),
|
||||
Type = PathType.PERFECT_CURVE
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(100, 150),
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(75, -50),
|
||||
Type = PathType.PERFECT_CURVE
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(225, -75),
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(350, 50),
|
||||
Type = PathType.PERFECT_CURVE
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(500, -75),
|
||||
},
|
||||
new PathControlPoint
|
||||
{
|
||||
Position = new Vector2(350, -120),
|
||||
},
|
||||
];
|
||||
|
||||
Vector2 oldStartPos = default;
|
||||
Vector2 oldEndPos = default;
|
||||
double oldDistance = default;
|
||||
|
||||
var oldControlPointTypes = segmentedSliderPath.Select(p => p.Type);
|
||||
|
||||
AddStep("Add slider", () =>
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
Position = new Vector2(0, 200),
|
||||
Path = new SliderPath(segmentedSliderPath)
|
||||
{
|
||||
ExpectedDistance = { Value = 1314 }
|
||||
}
|
||||
};
|
||||
|
||||
EditorBeatmap.Add(slider);
|
||||
|
||||
oldStartPos = slider.Position;
|
||||
oldEndPos = slider.EndPosition;
|
||||
oldDistance = slider.Path.Distance;
|
||||
});
|
||||
|
||||
AddStep("Select slider", () =>
|
||||
{
|
||||
var slider = (Slider)EditorBeatmap.HitObjects[0];
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
});
|
||||
|
||||
AddRepeatStep("Reverse slider", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.Key(Key.G);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
}, 3);
|
||||
|
||||
AddAssert("First arc's control is not at the slider's middle", () =>
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.PositionAt(0.5)) > 1
|
||||
);
|
||||
|
||||
AddAssert("Last arc's control is not at the slider's middle", () =>
|
||||
Vector2.Distance(selectedSlider.Path.ControlPoints[1].Position, selectedSlider.Path.PositionAt(0.5)) > 1
|
||||
);
|
||||
|
||||
AddAssert("First arc centered middle control point", () =>
|
||||
{
|
||||
var pathControlPoints = selectedSlider.Path.ControlPoints;
|
||||
float middleToStart = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[0].Position);
|
||||
float middleToEnd = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[2].Position);
|
||||
|
||||
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
|
||||
});
|
||||
|
||||
AddAssert("Last arc centered middle control point", () =>
|
||||
{
|
||||
var pathControlPoints = selectedSlider.Path.ControlPoints;
|
||||
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^3].Position);
|
||||
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
|
||||
|
||||
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
|
||||
});
|
||||
|
||||
AddAssert("Slider has correct length", () =>
|
||||
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
@@ -102,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f)));
|
||||
ClassicAssert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
double theta = circularArcProperties.ThetaStart + (circularArcProperties.Direction * progress * circularArcProperties.ThetaRange);
|
||||
Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius;
|
||||
|
||||
Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f),
|
||||
ClassicAssert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f),
|
||||
"A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints"
|
||||
+ ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress)
|
||||
+ " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress))
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModEasy : OsuModTestScene
|
||||
{
|
||||
protected override bool AllowFail => true;
|
||||
|
||||
[Test]
|
||||
public void TestMultipleApplication()
|
||||
{
|
||||
bool reapplied = false;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mods = [new OsuModEasy { Retries = { Value = 1 } }],
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () =>
|
||||
{
|
||||
// do stuff to speed up fails
|
||||
var b = new TestBeatmap(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
Difficulty = { DrainRate = 10 }
|
||||
};
|
||||
|
||||
foreach (var ho in b.HitObjects)
|
||||
ho.StartTime /= 4;
|
||||
|
||||
return b;
|
||||
},
|
||||
PassCondition = () =>
|
||||
{
|
||||
if (((ModEasyTestPlayer)Player).FailuresSuppressed > 0 && !reapplied)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToDifficulty>())
|
||||
mod.ApplyToDifficulty(new BeatmapDifficulty());
|
||||
|
||||
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToPlayer>())
|
||||
mod.ApplyToPlayer(Player);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// don't care if this fails. in fact a failure here is probably better than the alternative.
|
||||
}
|
||||
finally
|
||||
{
|
||||
reapplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
return Player.GameplayState.HasFailed && ((ModEasyTestPlayer)Player).FailuresSuppressed <= 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModEasyTestPlayer(CurrentTestData, AllowFail);
|
||||
|
||||
private partial class ModEasyTestPlayer : ModTestPlayer
|
||||
{
|
||||
public int FailuresSuppressed { get; private set; }
|
||||
|
||||
public ModEasyTestPlayer(ModTestData data, bool allowFail)
|
||||
: base(data, allowFail)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool CheckModsAllowFailure()
|
||||
{
|
||||
bool failureAllowed = GameplayState.Mods.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
|
||||
|
||||
if (!failureAllowed)
|
||||
FailuresSuppressed++;
|
||||
|
||||
return failureAllowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,30 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name)
|
||||
=> Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic());
|
||||
|
||||
[TestCase(239, "diffcalc-test")]
|
||||
[TestCase(54, "zero-length-sliders")]
|
||||
[TestCase(4, "very-fast-slider")]
|
||||
public void TestOffsetChanges(int expectedMaxCombo, string name)
|
||||
{
|
||||
const double offset_iterations = 400;
|
||||
var beatmap = GetBeatmap(name);
|
||||
|
||||
var attributes = CreateDifficultyCalculator(beatmap).Calculate();
|
||||
double expectedStarRating = attributes.StarRating;
|
||||
|
||||
for (int i = 0; i < offset_iterations; i++)
|
||||
{
|
||||
foreach (var beatmapHitObject in beatmap.Beatmap.HitObjects)
|
||||
beatmapHitObject.StartTime++;
|
||||
|
||||
attributes = CreateDifficultyCalculator(beatmap).Calculate();
|
||||
|
||||
// Platform-dependent math functions (Pow, Cbrt, Exp, etc) may result in minute differences.
|
||||
Assert.That(attributes.StarRating, Is.EqualTo(expectedStarRating).Within(0.00001));
|
||||
Assert.That(attributes.MaxCombo, Is.EqualTo(expectedMaxCombo));
|
||||
}
|
||||
}
|
||||
|
||||
protected override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset().RulesetInfo, beatmap);
|
||||
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -57,7 +58,7 @@ SliderTickRate:0.5
|
||||
|
||||
// The last hitobject triggers the stacking
|
||||
for (int i = 0; i < objects.Count - 1; i++)
|
||||
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
|
||||
ClassicAssert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +105,7 @@ SliderTickRate:1
|
||||
|
||||
// The last hitobject triggers the stacking
|
||||
for (int i = 0; i < objects.Count - 1; i++)
|
||||
Assert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
|
||||
ClassicAssert.AreEqual(0, ((OsuHitObject)objects[i]).StackHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageReference Include="Moq" Version="4.20.72" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Aim
|
||||
{
|
||||
public static class AgilityEvaluator
|
||||
{
|
||||
private const double distance_cap = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.2; // 1.2 circles distance between centers
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of fast aiming
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||
|
||||
double travelDistance = osuPrevObj?.LazyTravelDistance ?? 0;
|
||||
double distance = travelDistance + osuCurrObj.LazyJumpDistance;
|
||||
|
||||
double distanceScaled = Math.Min(distance, distance_cap) / distance_cap;
|
||||
|
||||
double agilityDifficulty = distanceScaled * 1000 / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
agilityDifficulty *= Math.Pow(osuCurrObj.SmallCircleBonus, 1.5);
|
||||
|
||||
agilityDifficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
|
||||
|
||||
return agilityDifficulty;
|
||||
}
|
||||
|
||||
private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.2, ms / 1000));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Aim
|
||||
{
|
||||
public static class FlowAimEvaluator
|
||||
{
|
||||
private const double velocity_change_multiplier = 0.52;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates difficulty of "flow aim" - aiming pattern where player doesn't stop their cursor on every object and instead "flows" through them.
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance)
|
||||
{
|
||||
if (current.BaseObject is Spinner || current.Index <= 1 || current.Previous(0).BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||
|
||||
double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpDistance : osuCurrObj.JumpDistance;
|
||||
double prevDistance = withSliderTravelDistance ? osuLastObj.LazyJumpDistance : osuLastObj.JumpDistance;
|
||||
|
||||
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
// If the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.LazyJumpDistance;
|
||||
currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
|
||||
}
|
||||
|
||||
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
|
||||
|
||||
double flowDifficulty = currVelocity;
|
||||
|
||||
// Apply high circle size bonus to the base velocity.
|
||||
// We use reduced CS bonus here because the bonus was made for an evaluator with a different d/t scaling
|
||||
flowDifficulty *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
|
||||
|
||||
// Rhythm changes are harder to flow
|
||||
flowDifficulty *= 1 + Math.Min(0.25,
|
||||
Math.Pow((Math.Max(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime) - Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime)) / 50, 4));
|
||||
|
||||
if (osuCurrObj.Angle != null && osuLastObj.Angle != null)
|
||||
{
|
||||
double angleDifference = Math.Abs(osuCurrObj.Angle.Value - osuLastObj.Angle.Value);
|
||||
double angleDifferenceAdjusted = Math.Sin(angleDifference / 2) * 180.0;
|
||||
double angularVelocity = angleDifferenceAdjusted / (osuCurrObj.AdjustedDeltaTime * 0.1);
|
||||
|
||||
// Low angular velocity flow (angles are consistent) is easier to follow than erratic flow
|
||||
flowDifficulty *= 0.8 + Math.Sqrt(angularVelocity / 270.0);
|
||||
}
|
||||
|
||||
// If all three notes are overlapping - don't reward bonuses as you don't have to do additional movement
|
||||
double overlappedNotesWeight = 1;
|
||||
|
||||
if (current.Index > 2)
|
||||
{
|
||||
double o1 = calculateOverlapFactor(osuCurrObj, osuLastObj);
|
||||
double o2 = calculateOverlapFactor(osuCurrObj, osuLastLastObj);
|
||||
double o3 = calculateOverlapFactor(osuLastObj, osuLastLastObj);
|
||||
|
||||
overlappedNotesWeight = 1 - o1 * o2 * o3;
|
||||
}
|
||||
|
||||
if (osuCurrObj.Angle != null)
|
||||
{
|
||||
// Acute angles are also hard to flow
|
||||
flowDifficulty += currVelocity *
|
||||
SnapAimEvaluator.CalcAngleAcuteness(osuCurrObj.Angle.Value) *
|
||||
overlappedNotesWeight;
|
||||
}
|
||||
|
||||
if (Math.Max(prevVelocity, currVelocity) != 0)
|
||||
{
|
||||
if (withSliderTravelDistance)
|
||||
{
|
||||
currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
|
||||
}
|
||||
|
||||
// Scale with ratio of difference compared to 0.5 * max dist.
|
||||
double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1);
|
||||
|
||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||
double overlapVelocityBuff = Math.Min(OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25 / Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime),
|
||||
Math.Abs(prevVelocity - currVelocity));
|
||||
|
||||
flowDifficulty += overlapVelocityBuff *
|
||||
distRatio *
|
||||
overlappedNotesWeight *
|
||||
velocity_change_multiplier;
|
||||
}
|
||||
|
||||
if (osuCurrObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
// Include slider velocity to make velocity more consistent with snap
|
||||
flowDifficulty += osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
|
||||
}
|
||||
|
||||
// Final velocity is being raised to a power because flow difficulty scales harder with both high distance and time, and we want to account for that
|
||||
flowDifficulty = Math.Pow(flowDifficulty, 1.45);
|
||||
|
||||
// Reduce difficulty for low spacing since spacing below radius is always to be flowed
|
||||
return flowDifficulty * DifficultyCalculationUtils.Smootherstep(currDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
|
||||
}
|
||||
|
||||
private static double calculateOverlapFactor(OsuDifficultyHitObject first, OsuDifficultyHitObject second)
|
||||
{
|
||||
var firstBase = (OsuHitObject)first.BaseObject;
|
||||
var secondBase = (OsuHitObject)second.BaseObject;
|
||||
double objectRadius = firstBase.Radius;
|
||||
|
||||
double distance = Vector2.Distance(firstBase.StackedPosition, secondBase.StackedPosition);
|
||||
return Math.Clamp(1 - Math.Pow(Math.Max(distance - objectRadius, 0) / objectRadius, 2), 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Aim
|
||||
{
|
||||
public static class SnapAimEvaluator
|
||||
{
|
||||
private const double wide_angle_multiplier = 9.67;
|
||||
private const double acute_angle_multiplier = 2.41;
|
||||
private const double slider_multiplier = 1.5;
|
||||
private const double velocity_change_multiplier = 0.9;
|
||||
private const double wiggle_multiplier = 1.02; // WARNING: Increasing this multiplier beyond 1.02 reduces difficulty as distance increases. Refer to the desmos link above the wiggle bonus calculation
|
||||
private const double maximum_repetition_nerf = 0.15;
|
||||
private const double maximum_vector_influence = 0.5;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of aiming the current object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cursor velocity to the current object,</description></item>
|
||||
/// <item><description>angle difficulty,</description></item>
|
||||
/// <item><description>sharp velocity increases,</description></item>
|
||||
/// <item><description>and slider difficulty.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance)
|
||||
{
|
||||
if (current.BaseObject is Spinner || current.Index <= 1 || current.Previous(0).BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||
var osuLast2Obj = (OsuDifficultyHitObject)current.Previous(2);
|
||||
|
||||
const int radius = OsuDifficultyHitObject.NORMALISED_RADIUS;
|
||||
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
|
||||
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currDistance = withSliderTravelDistance ? osuCurrObj.LazyJumpDistance : osuCurrObj.JumpDistance;
|
||||
double currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.LazyJumpDistance;
|
||||
currVelocity = Math.Max(currVelocity, sliderDistance / osuCurrObj.AdjustedDeltaTime);
|
||||
}
|
||||
|
||||
double prevDistance = withSliderTravelDistance ? osuLastObj.LazyJumpDistance : osuLastObj.JumpDistance;
|
||||
double prevVelocity = prevDistance / osuLastObj.AdjustedDeltaTime;
|
||||
|
||||
double snapDifficulty = currVelocity; // Start difficulty with regular velocity.
|
||||
|
||||
// Penalize angle repetition.
|
||||
snapDifficulty *= vectorAngleRepetition(osuCurrObj, osuLastObj);
|
||||
|
||||
if (osuCurrObj.Angle != null && osuLastObj.Angle != null)
|
||||
{
|
||||
double currAngle = osuCurrObj.Angle.Value;
|
||||
double lastAngle = osuLastObj.Angle.Value;
|
||||
|
||||
// Rewarding angles, take the smaller velocity as base.
|
||||
double velocityInfluence = Math.Min(currVelocity, prevVelocity);
|
||||
|
||||
double acuteAngleBonus = 0;
|
||||
|
||||
if (Math.Max(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime) < 1.25 * Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime)) // If rhythms are the same.
|
||||
{
|
||||
acuteAngleBonus = CalcAngleAcuteness(currAngle);
|
||||
|
||||
// Penalize angle repetition. It is important to do it _before_ multiplying by anything because we compare raw acuteness here
|
||||
acuteAngleBonus *= 0.08 + 0.92 * (1 - Math.Min(acuteAngleBonus, Math.Pow(CalcAngleAcuteness(lastAngle), 3)));
|
||||
|
||||
// Apply acute angle bonus for BPM above 300 1/2 and distance more than one diameter
|
||||
acuteAngleBonus *= velocityInfluence * DifficultyCalculationUtils.Smootherstep(DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.AdjustedDeltaTime, 2), 300, 400) *
|
||||
DifficultyCalculationUtils.Smootherstep(currDistance, 0, diameter * 2);
|
||||
}
|
||||
|
||||
double wideAngleBonus = calcAngleWideness(currAngle);
|
||||
|
||||
// Penalize angle repetition. It is important to do it _before_ multiplying by velocity because we compare raw wideness here
|
||||
wideAngleBonus *= 0.25 + 0.75 * (1 - Math.Min(wideAngleBonus, Math.Pow(calcAngleWideness(lastAngle), 3)));
|
||||
|
||||
// Rescaling velocity for the wide angle bonus
|
||||
const double wide_angle_time_scale = 1.45;
|
||||
double wideAngleCurrVelocity = currDistance / Math.Pow(osuCurrObj.AdjustedDeltaTime, wide_angle_time_scale);
|
||||
double wideAnglePrevVelocity = prevDistance / Math.Pow(osuLastObj.AdjustedDeltaTime, wide_angle_time_scale);
|
||||
|
||||
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
double sliderDistance = osuLastObj.LazyTravelDistance + osuCurrObj.LazyJumpDistance;
|
||||
wideAngleCurrVelocity = Math.Max(wideAngleCurrVelocity, sliderDistance / Math.Pow(osuCurrObj.AdjustedDeltaTime, wide_angle_time_scale));
|
||||
}
|
||||
|
||||
wideAngleBonus *= Math.Min(wideAngleCurrVelocity, wideAnglePrevVelocity);
|
||||
|
||||
if (osuLast2Obj != null)
|
||||
{
|
||||
// If objects just go back and forth through a middle point - don't give as much wide bonus
|
||||
// Use Previous(2) and Previous(0) because angles calculation is done prevprev-prev-curr, so any object's angle's center point is always the previous object
|
||||
var lastBaseObject = (OsuHitObject)osuLastObj.BaseObject;
|
||||
var last2BaseObject = (OsuHitObject)osuLast2Obj.BaseObject;
|
||||
|
||||
float distance = (last2BaseObject.StackedPosition - lastBaseObject.StackedPosition).Length;
|
||||
|
||||
if (distance < 1)
|
||||
{
|
||||
wideAngleBonus *= 1 - 0.55 * (1 - distance);
|
||||
}
|
||||
}
|
||||
|
||||
// Add in acute angle bonus or wide angle bonus, whichever is larger.
|
||||
snapDifficulty += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier);
|
||||
|
||||
// Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle
|
||||
// https://www.desmos.com/calculator/dp0v0nvowc
|
||||
double wiggleBonus = velocityInfluence
|
||||
* DifficultyCalculationUtils.Smootherstep(currDistance, radius, diameter)
|
||||
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(currDistance, diameter * 3, diameter), 1.8)
|
||||
* DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60))
|
||||
* DifficultyCalculationUtils.Smootherstep(prevDistance, radius, diameter)
|
||||
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(prevDistance, diameter * 3, diameter), 1.8)
|
||||
* DifficultyCalculationUtils.Smootherstep(lastAngle, double.DegreesToRadians(110), double.DegreesToRadians(60));
|
||||
|
||||
snapDifficulty += wiggleBonus * wiggle_multiplier;
|
||||
}
|
||||
|
||||
if (Math.Max(prevVelocity, currVelocity) != 0)
|
||||
{
|
||||
if (withSliderTravelDistance)
|
||||
{
|
||||
// We want to use just the object jump without slider velocity when awarding differences
|
||||
currVelocity = currDistance / osuCurrObj.AdjustedDeltaTime;
|
||||
}
|
||||
|
||||
// Scale with ratio of difference compared to 0.5 * max dist.
|
||||
double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1);
|
||||
|
||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||
double overlapVelocityBuff = Math.Min(diameter * 1.25 / Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime), Math.Abs(prevVelocity - currVelocity));
|
||||
|
||||
double velocityChangeBonus = overlapVelocityBuff * distRatio;
|
||||
|
||||
// Penalize for rhythm changes.
|
||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime) / Math.Max(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime), 2);
|
||||
|
||||
snapDifficulty += velocityChangeBonus * velocity_change_multiplier;
|
||||
}
|
||||
|
||||
// Reward sliders based on velocity.
|
||||
if (osuCurrObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
double sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime;
|
||||
snapDifficulty += (sliderBonus < 1 ? sliderBonus : Math.Pow(sliderBonus, 0.75)) * slider_multiplier;
|
||||
}
|
||||
|
||||
// Apply high circle size bonus
|
||||
snapDifficulty *= osuCurrObj.SmallCircleBonus;
|
||||
|
||||
snapDifficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
|
||||
|
||||
return snapDifficulty;
|
||||
}
|
||||
|
||||
private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.03, Math.Pow(ms / 1000, 0.65)));
|
||||
|
||||
private static double vectorAngleRepetition(OsuDifficultyHitObject current, OsuDifficultyHitObject previous)
|
||||
{
|
||||
if (current.Angle == null || previous.Angle == null)
|
||||
return 1;
|
||||
|
||||
const double note_limit = 6;
|
||||
|
||||
double constantAngleCount = 0;
|
||||
|
||||
for (int index = 0; index < note_limit; index++)
|
||||
{
|
||||
var loopObj = (OsuDifficultyHitObject)current.Previous(index);
|
||||
|
||||
if (loopObj.IsNull())
|
||||
break;
|
||||
|
||||
// Only consider vectors in the same jump section, stopping to change rhythm ruins momentum
|
||||
if (Math.Max(current.AdjustedDeltaTime, loopObj.AdjustedDeltaTime) > 1.1 * Math.Min(current.AdjustedDeltaTime, loopObj.AdjustedDeltaTime))
|
||||
break;
|
||||
|
||||
if (loopObj.NormalisedVectorAngle.IsNotNull() && current.NormalisedVectorAngle.IsNotNull())
|
||||
{
|
||||
double angleDifference = Math.Abs(current.NormalisedVectorAngle.Value - loopObj.NormalisedVectorAngle.Value);
|
||||
// Refer to this desmos for tuning, constants need to be precise so that values stay within the range of 0 and 1.
|
||||
// https://www.desmos.com/calculator/a8jesv5sv2
|
||||
constantAngleCount += Math.Cos(8 * Math.Min(double.DegreesToRadians(11.25), angleDifference));
|
||||
}
|
||||
}
|
||||
|
||||
double vectorRepetition = Math.Pow(Math.Min(0.5 / constantAngleCount, 1), 2);
|
||||
|
||||
double stackFactor = DifficultyCalculationUtils.Smootherstep(current.LazyJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_DIAMETER);
|
||||
|
||||
double currAngle = current.Angle.Value;
|
||||
double lastAngle = previous.Angle.Value;
|
||||
|
||||
double angleDifferenceAdjusted = Math.Cos(2 * Math.Min(double.DegreesToRadians(45), Math.Abs(currAngle - lastAngle) * stackFactor));
|
||||
|
||||
double baseNerf = 1 - maximum_repetition_nerf * CalcAngleAcuteness(lastAngle) * angleDifferenceAdjusted;
|
||||
|
||||
return Math.Pow(baseNerf + (1 - baseNerf) * vectorRepetition * maximum_vector_influence * stackFactor, 2);
|
||||
}
|
||||
|
||||
private static double calcAngleWideness(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140));
|
||||
|
||||
public static double CalcAngleAcuteness(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40));
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
{
|
||||
public static class AimEvaluator
|
||||
{
|
||||
private const double wide_angle_multiplier = 1.5;
|
||||
private const double acute_angle_multiplier = 2.55;
|
||||
private const double slider_multiplier = 1.35;
|
||||
private const double velocity_change_multiplier = 0.75;
|
||||
private const double wiggle_multiplier = 1.02;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of aiming the current object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cursor velocity to the current object,</description></item>
|
||||
/// <item><description>angle difficulty,</description></item>
|
||||
/// <item><description>sharp velocity increases,</description></item>
|
||||
/// <item><description>and slider difficulty.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool withSliderTravelDistance)
|
||||
{
|
||||
if (current.BaseObject is Spinner || current.Index <= 1 || current.Previous(0).BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuLastObj = (OsuDifficultyHitObject)current.Previous(0);
|
||||
var osuLastLastObj = (OsuDifficultyHitObject)current.Previous(1);
|
||||
var osuLast2Obj = (OsuDifficultyHitObject)current.Previous(2);
|
||||
|
||||
const int radius = OsuDifficultyHitObject.NORMALISED_RADIUS;
|
||||
const int diameter = OsuDifficultyHitObject.NORMALISED_DIAMETER;
|
||||
|
||||
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||
double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
|
||||
if (osuLastObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||
double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object
|
||||
|
||||
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
|
||||
}
|
||||
|
||||
// As above, do the same for the previous hitobject.
|
||||
double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.AdjustedDeltaTime;
|
||||
|
||||
if (osuLastLastObj.BaseObject is Slider && withSliderTravelDistance)
|
||||
{
|
||||
double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime;
|
||||
double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime;
|
||||
|
||||
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
|
||||
}
|
||||
|
||||
double wideAngleBonus = 0;
|
||||
double acuteAngleBonus = 0;
|
||||
double sliderBonus = 0;
|
||||
double velocityChangeBonus = 0;
|
||||
double wiggleBonus = 0;
|
||||
|
||||
double aimStrain = currVelocity; // Start strain with regular velocity.
|
||||
|
||||
if (osuCurrObj.Angle != null && osuLastObj.Angle != null)
|
||||
{
|
||||
double currAngle = osuCurrObj.Angle.Value;
|
||||
double lastAngle = osuLastObj.Angle.Value;
|
||||
|
||||
// Rewarding angles, take the smaller velocity as base.
|
||||
double angleBonus = Math.Min(currVelocity, prevVelocity);
|
||||
|
||||
if (Math.Max(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime) < 1.25 * Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime)) // If rhythms are the same.
|
||||
{
|
||||
acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
||||
|
||||
// Penalize angle repetition.
|
||||
acuteAngleBonus *= 0.08 + 0.92 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastAngle), 3)));
|
||||
|
||||
// Apply acute angle bonus for BPM above 300 1/2 and distance more than one diameter
|
||||
acuteAngleBonus *= angleBonus *
|
||||
DifficultyCalculationUtils.Smootherstep(DifficultyCalculationUtils.MillisecondsToBPM(osuCurrObj.AdjustedDeltaTime, 2), 300, 400) *
|
||||
DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, diameter, diameter * 2);
|
||||
}
|
||||
|
||||
wideAngleBonus = calcWideAngleBonus(currAngle);
|
||||
|
||||
// Penalize angle repetition.
|
||||
wideAngleBonus *= 1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3));
|
||||
|
||||
// Apply full wide angle bonus for distance more than one diameter
|
||||
wideAngleBonus *= angleBonus * DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, 0, diameter);
|
||||
|
||||
// Apply wiggle bonus for jumps that are [radius, 3*diameter] in distance, with < 110 angle
|
||||
// https://www.desmos.com/calculator/dp0v0nvowc
|
||||
wiggleBonus = angleBonus
|
||||
* DifficultyCalculationUtils.Smootherstep(osuCurrObj.LazyJumpDistance, radius, diameter)
|
||||
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuCurrObj.LazyJumpDistance, diameter * 3, diameter), 1.8)
|
||||
* DifficultyCalculationUtils.Smootherstep(currAngle, double.DegreesToRadians(110), double.DegreesToRadians(60))
|
||||
* DifficultyCalculationUtils.Smootherstep(osuLastObj.LazyJumpDistance, radius, diameter)
|
||||
* Math.Pow(DifficultyCalculationUtils.ReverseLerp(osuLastObj.LazyJumpDistance, diameter * 3, diameter), 1.8)
|
||||
* DifficultyCalculationUtils.Smootherstep(lastAngle, double.DegreesToRadians(110), double.DegreesToRadians(60));
|
||||
|
||||
if (osuLast2Obj != null)
|
||||
{
|
||||
// If objects just go back and forth through a middle point - don't give as much wide bonus
|
||||
// Use Previous(2) and Previous(0) because angles calculation is done prevprev-prev-curr, so any object's angle's center point is always the previous object
|
||||
var lastBaseObject = (OsuHitObject)osuLastObj.BaseObject;
|
||||
var last2BaseObject = (OsuHitObject)osuLast2Obj.BaseObject;
|
||||
|
||||
float distance = (last2BaseObject.StackedPosition - lastBaseObject.StackedPosition).Length;
|
||||
|
||||
if (distance < 1)
|
||||
{
|
||||
wideAngleBonus *= 1 - 0.35 * (1 - distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Max(prevVelocity, currVelocity) != 0)
|
||||
{
|
||||
// We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities.
|
||||
prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.AdjustedDeltaTime;
|
||||
currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.AdjustedDeltaTime;
|
||||
|
||||
// Scale with ratio of difference compared to 0.5 * max dist.
|
||||
double distRatio = DifficultyCalculationUtils.Smoothstep(Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity), 0, 1);
|
||||
|
||||
// Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing.
|
||||
double overlapVelocityBuff = Math.Min(diameter * 1.25 / Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime), Math.Abs(prevVelocity - currVelocity));
|
||||
|
||||
velocityChangeBonus = overlapVelocityBuff * distRatio;
|
||||
|
||||
// Penalize for rhythm changes.
|
||||
velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime) / Math.Max(osuCurrObj.AdjustedDeltaTime, osuLastObj.AdjustedDeltaTime), 2);
|
||||
}
|
||||
|
||||
if (osuLastObj.BaseObject is Slider)
|
||||
{
|
||||
// Reward sliders based on velocity.
|
||||
sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||
}
|
||||
|
||||
aimStrain += wiggleBonus * wiggle_multiplier;
|
||||
aimStrain += velocityChangeBonus * velocity_change_multiplier;
|
||||
|
||||
// Add in acute angle bonus or wide angle bonus, whichever is larger.
|
||||
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier);
|
||||
|
||||
// Apply high circle size bonus
|
||||
aimStrain *= osuCurrObj.SmallCircleBonus;
|
||||
|
||||
// Add in additional slider velocity bonus.
|
||||
if (withSliderTravelDistance)
|
||||
aimStrain += sliderBonus * slider_multiplier;
|
||||
|
||||
return aimStrain;
|
||||
}
|
||||
|
||||
private static double calcWideAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(40), double.DegreesToRadians(140));
|
||||
|
||||
private static double calcAcuteAngleBonus(double angle) => DifficultyCalculationUtils.Smoothstep(angle, double.DegreesToRadians(140), double.DegreesToRadians(40));
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
@@ -28,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
/// <item><description>and whether the hidden mod is enabled.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden)
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnlyList<Mod> mods)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
@@ -40,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
double smallDistNerf = 1.0;
|
||||
double cumulativeStrainTime = 0.0;
|
||||
|
||||
double result = 0.0;
|
||||
double flashlightDifficulty = 0.0;
|
||||
|
||||
OsuDifficultyHitObject lastObj = osuCurrent;
|
||||
|
||||
@@ -66,9 +70,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0);
|
||||
|
||||
// Bonus based on how visible the object is.
|
||||
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, hidden));
|
||||
double opacityBonus = 1.0 + max_opacity_bonus * (1.0 - osuCurrent.OpacityAt(currentHitObject.StartTime, mods.OfType<OsuModHidden>().Any(m => !m.OnlyFadeApproachCircles.Value)));
|
||||
|
||||
result += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime;
|
||||
flashlightDifficulty += stackNerf * opacityBonus * scalingFactor * jumpDistance / cumulativeStrainTime;
|
||||
|
||||
if (currentObj.Angle != null && osuCurrent.Angle != null)
|
||||
{
|
||||
@@ -81,14 +85,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
lastObj = currentObj;
|
||||
}
|
||||
|
||||
result = Math.Pow(smallDistNerf * result, 2.0);
|
||||
flashlightDifficulty = Math.Pow(smallDistNerf * flashlightDifficulty, 2.0);
|
||||
|
||||
// Additional bonus for Hidden due to there being no approach circles.
|
||||
if (hidden)
|
||||
result *= 1.0 + hidden_bonus;
|
||||
if (mods.OfType<OsuModHidden>().Any())
|
||||
flashlightDifficulty *= 1.0 + hidden_bonus;
|
||||
|
||||
// Nerf patterns with repeated angles.
|
||||
result *= min_angle_multiplier + (1.0 - min_angle_multiplier) / (angleRepeatCount + 1.0);
|
||||
flashlightDifficulty *= min_angle_multiplier + (1.0 - min_angle_multiplier) / (angleRepeatCount + 1.0);
|
||||
|
||||
double sliderBonus = 0.0;
|
||||
|
||||
@@ -108,9 +112,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
sliderBonus /= (osuSlider.RepeatCount + 1);
|
||||
}
|
||||
|
||||
result += sliderBonus * slider_multiplier;
|
||||
flashlightDifficulty += sliderBonus * slider_multiplier;
|
||||
|
||||
return result;
|
||||
return flashlightDifficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
{
|
||||
public static class ReadingEvaluator
|
||||
{
|
||||
private const double reading_window_size = 3000; // 3 seconds
|
||||
private const double distance_influence_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.5; // 1.5 circles distance between centers
|
||||
private const double hidden_multiplier = 0.28;
|
||||
private const double density_multiplier = 2.4;
|
||||
private const double density_difficulty_base = 2.5;
|
||||
private const double preempt_balancing_factor = 140000;
|
||||
private const double preempt_starting_point = 500; // AR 9.66 in milliseconds
|
||||
private const double minimum_angle_relevancy_time = 2000; // 2 seconds
|
||||
private const double maximum_angle_relevancy_time = 200;
|
||||
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, bool hidden)
|
||||
{
|
||||
if (current.BaseObject is Spinner || current.Index == 0)
|
||||
return 0;
|
||||
|
||||
var currObj = (OsuDifficultyHitObject)current;
|
||||
var nextObj = (OsuDifficultyHitObject)current.Next(0);
|
||||
|
||||
double velocity = Math.Max(1, currObj.LazyJumpDistance / currObj.AdjustedDeltaTime); // Only allow velocity to buff
|
||||
|
||||
double currentVisibleObjectDensity = retrieveCurrentVisibleObjectDensity(currObj);
|
||||
double pastObjectDifficultyInfluence = getPastObjectDifficultyInfluence(currObj);
|
||||
|
||||
double constantAngleNerfFactor = getConstantAngleNerfFactor(currObj);
|
||||
|
||||
double noteDensityDifficulty = calculateDensityDifficulty(nextObj, velocity, constantAngleNerfFactor, pastObjectDifficultyInfluence, currentVisibleObjectDensity);
|
||||
|
||||
double hiddenDifficulty = hidden
|
||||
? calculateHiddenDifficulty(currObj, pastObjectDifficultyInfluence, currentVisibleObjectDensity, velocity, constantAngleNerfFactor)
|
||||
: 0;
|
||||
|
||||
double preemptDifficulty = calculatePreemptDifficulty(velocity, constantAngleNerfFactor, currObj.Preempt);
|
||||
|
||||
double readingDifficulty = DifficultyCalculationUtils.Norm(1.5, preemptDifficulty, hiddenDifficulty, noteDensityDifficulty);
|
||||
|
||||
// Having less time to process information is harder
|
||||
readingDifficulty *= highBpmBonus(currObj.AdjustedDeltaTime);
|
||||
|
||||
return readingDifficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the density difficulty of the current object and how hard it is to aim it because of it based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cursor velocity to the current object,</description></item>
|
||||
/// <item><description>how many times the current object's angle was repeated,</description></item>
|
||||
/// <item><description>density of objects visible when the current object appears,</description></item>
|
||||
/// <item><description>density of objects visible when the current object needs to be clicked,</description></item>
|
||||
/// /// </list>
|
||||
/// </summary>
|
||||
private static double calculateDensityDifficulty(OsuDifficultyHitObject? nextObj, double velocity, double constantAngleNerfFactor,
|
||||
double pastObjectDifficultyInfluence, double currentVisibleObjectDensity)
|
||||
{
|
||||
// Consider future densities too because it can make the path the cursor takes less clear
|
||||
double futureObjectDifficultyInfluence = Math.Sqrt(currentVisibleObjectDensity);
|
||||
|
||||
if (nextObj != null)
|
||||
{
|
||||
// Reduce difficulty if movement to next object is small
|
||||
futureObjectDifficultyInfluence *= DifficultyCalculationUtils.Smootherstep(nextObj.LazyJumpDistance, 15, distance_influence_threshold);
|
||||
}
|
||||
|
||||
// Value higher note densities exponentially
|
||||
double noteDensityDifficulty = Math.Pow(pastObjectDifficultyInfluence + futureObjectDifficultyInfluence, 1.7) * 0.4 * constantAngleNerfFactor * velocity;
|
||||
|
||||
// Award only denser than average maps.
|
||||
noteDensityDifficulty = Math.Max(0, noteDensityDifficulty - density_difficulty_base);
|
||||
|
||||
// Apply a soft cap to general density reading to account for partial memorization
|
||||
noteDensityDifficulty = Math.Pow(noteDensityDifficulty, 0.45) * density_multiplier;
|
||||
|
||||
return noteDensityDifficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of aiming the current object when the approach rate is very high based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cursor velocity to the current object,</description></item>
|
||||
/// <item><description>how many times the current object's angle was repeated,</description></item>
|
||||
/// <item><description>how many milliseconds elapse between the approach circle appearing and touching the inner circle</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
private static double calculatePreemptDifficulty(double velocity, double constantAngleNerfFactor, double preempt)
|
||||
{
|
||||
// Arbitrary curve for the base value preempt difficulty should have as approach rate increases.
|
||||
// https://www.desmos.com/calculator/c175335a71
|
||||
double preemptDifficulty = Math.Pow((preempt_starting_point - preempt + Math.Abs(preempt - preempt_starting_point)) / 2, 2.5) / preempt_balancing_factor;
|
||||
|
||||
preemptDifficulty *= constantAngleNerfFactor * velocity;
|
||||
|
||||
return preemptDifficulty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the difficulty of aiming the current object when the hidden mod is active based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>cursor velocity to the current object,</description></item>
|
||||
/// <item><description>time the current object spends invisible,</description></item>
|
||||
/// <item><description>density of objects visible when the current object appears,</description></item>
|
||||
/// <item><description>density of objects visible when the current object needs to be clicked,</description></item>
|
||||
/// <item><description>how many times the current object's angle was repeated,</description></item>
|
||||
/// <item><description>if the current object is perfectly stacked to the previous one</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
private static double calculateHiddenDifficulty(OsuDifficultyHitObject currObj, double pastObjectDifficultyInfluence, double currentVisibleObjectDensity, double velocity,
|
||||
double constantAngleNerfFactor)
|
||||
{
|
||||
// Higher preempt means that time spent invisible is higher too, we want to reward that
|
||||
double preemptFactor = Math.Pow(currObj.Preempt, 2.2) * 0.01;
|
||||
|
||||
// Account for both past and current densities
|
||||
double densityFactor = Math.Pow(currentVisibleObjectDensity + pastObjectDifficultyInfluence, 3.3) * 3;
|
||||
|
||||
double hiddenDifficulty = (preemptFactor + densityFactor) * constantAngleNerfFactor * velocity * 0.01;
|
||||
|
||||
// Apply a soft cap to general HD reading to account for partial memorization
|
||||
hiddenDifficulty = Math.Pow(hiddenDifficulty, 0.4) * hidden_multiplier;
|
||||
|
||||
var previousObj = (OsuDifficultyHitObject)currObj.Previous(0);
|
||||
|
||||
// Buff perfect stacks only if current note is completely invisible at the time you click the previous note.
|
||||
if (currObj.LazyJumpDistance == 0 && currObj.OpacityAt(previousObj.BaseObject.StartTime, true) == 0 && previousObj.StartTime > currObj.StartTime - currObj.Preempt)
|
||||
hiddenDifficulty += hidden_multiplier * 2500 / Math.Pow(currObj.AdjustedDeltaTime, 1.5); // Perfect stacks are harder the less time between notes
|
||||
|
||||
return hiddenDifficulty;
|
||||
}
|
||||
|
||||
private static double getPastObjectDifficultyInfluence(OsuDifficultyHitObject currObj)
|
||||
{
|
||||
double pastObjectDifficultyInfluence = 0;
|
||||
|
||||
foreach (var loopObj in retrievePastVisibleObjects(currObj))
|
||||
{
|
||||
double loopDifficulty = currObj.OpacityAt(loopObj.BaseObject.StartTime, false);
|
||||
|
||||
// When aiming an object small distances mean previous objects may be cheesed, so it doesn't matter whether they were arranged confusingly.
|
||||
loopDifficulty *= DifficultyCalculationUtils.Smootherstep(loopObj.LazyJumpDistance, 15, distance_influence_threshold);
|
||||
|
||||
// Account less for objects close to the max reading window
|
||||
double timeBetweenCurrAndLoopObj = currObj.StartTime - loopObj.StartTime;
|
||||
double timeNerfFactor = getTimeNerfFactor(timeBetweenCurrAndLoopObj);
|
||||
|
||||
loopDifficulty *= timeNerfFactor;
|
||||
pastObjectDifficultyInfluence += loopDifficulty;
|
||||
}
|
||||
|
||||
return pastObjectDifficultyInfluence;
|
||||
}
|
||||
|
||||
// Returns a list of objects that are visible on screen at the point in time the current object becomes visible.
|
||||
private static IEnumerable<OsuDifficultyHitObject> retrievePastVisibleObjects(OsuDifficultyHitObject current)
|
||||
{
|
||||
for (int i = 0; i < current.Index; i++)
|
||||
{
|
||||
OsuDifficultyHitObject hitObject = (OsuDifficultyHitObject)current.Previous(i);
|
||||
|
||||
if (hitObject.IsNull() ||
|
||||
current.StartTime - hitObject.StartTime > reading_window_size ||
|
||||
hitObject.StartTime < current.StartTime - current.Preempt) // Current object not visible at the time object needs to be clicked
|
||||
break;
|
||||
|
||||
yield return hitObject;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the density of objects visible at the point in time the current object needs to be clicked capped by the reading window.
|
||||
private static double retrieveCurrentVisibleObjectDensity(OsuDifficultyHitObject current)
|
||||
{
|
||||
double visibleObjectCount = 0;
|
||||
|
||||
OsuDifficultyHitObject? hitObject = (OsuDifficultyHitObject)current.Next(0);
|
||||
|
||||
while (hitObject != null)
|
||||
{
|
||||
if (hitObject.StartTime - current.StartTime > reading_window_size ||
|
||||
current.StartTime < hitObject.StartTime - hitObject.Preempt) // Object not visible at the time current object needs to be clicked.
|
||||
break;
|
||||
|
||||
double timeBetweenCurrAndLoopObj = hitObject.StartTime - current.StartTime;
|
||||
double timeNerfFactor = getTimeNerfFactor(timeBetweenCurrAndLoopObj);
|
||||
|
||||
visibleObjectCount += hitObject.OpacityAt(current.BaseObject.StartTime, false) * timeNerfFactor;
|
||||
|
||||
hitObject = (OsuDifficultyHitObject?)hitObject.Next(0);
|
||||
}
|
||||
|
||||
return visibleObjectCount;
|
||||
}
|
||||
|
||||
// Returns a factor of how often the current object's angle has been repeated in a certain time frame.
|
||||
// It does this by checking the difference in angle between current and past objects and sums them based on a range of similarity.
|
||||
// https://www.desmos.com/calculator/eb057a4822
|
||||
private static double getConstantAngleNerfFactor(OsuDifficultyHitObject current)
|
||||
{
|
||||
double constantAngleCount = 0;
|
||||
int index = 0;
|
||||
double currentTimeGap = 0;
|
||||
|
||||
OsuDifficultyHitObject loopObjPrev0 = current;
|
||||
OsuDifficultyHitObject? loopObjPrev1 = null;
|
||||
OsuDifficultyHitObject? loopObjPrev2 = null;
|
||||
|
||||
while (currentTimeGap < minimum_angle_relevancy_time)
|
||||
{
|
||||
var loopObj = (OsuDifficultyHitObject)current.Previous(index);
|
||||
|
||||
if (loopObj.IsNull())
|
||||
break;
|
||||
|
||||
// Account less for objects that are close to the time limit.
|
||||
double longIntervalFactor = 1 - DifficultyCalculationUtils.ReverseLerp(loopObj.AdjustedDeltaTime, maximum_angle_relevancy_time, minimum_angle_relevancy_time);
|
||||
|
||||
if (loopObj.Angle.IsNotNull() && current.Angle.IsNotNull())
|
||||
{
|
||||
double angleDifference = Math.Abs(current.Angle.Value - loopObj.Angle.Value);
|
||||
double angleDifferenceAlternating = Math.PI;
|
||||
|
||||
if (loopObjPrev0.Angle != null && loopObjPrev1?.Angle != null && loopObjPrev2?.Angle != null)
|
||||
{
|
||||
angleDifferenceAlternating = Math.Abs(loopObjPrev1.Angle.Value - loopObj.Angle.Value);
|
||||
angleDifferenceAlternating += Math.Abs(loopObjPrev2.Angle.Value - loopObjPrev0.Angle.Value);
|
||||
|
||||
double weight = 1.0;
|
||||
|
||||
// Be sure that one of the angles is very sharp, when other is wide
|
||||
weight *= DifficultyCalculationUtils.ReverseLerp(Math.Min(loopObj.Angle.Value, loopObjPrev0.Angle.Value) * 180 / Math.PI, 20, 5);
|
||||
weight *= DifficultyCalculationUtils.ReverseLerp(Math.Max(loopObj.Angle.Value, loopObjPrev0.Angle.Value) * 180 / Math.PI, 60, 120);
|
||||
|
||||
// Lerp between max angle difference and rescaled alternating difference, with more harsh scaling compared to normal difference
|
||||
angleDifferenceAlternating = double.Lerp(Math.PI, 0.1 * angleDifferenceAlternating, weight);
|
||||
}
|
||||
|
||||
double stackFactor = DifficultyCalculationUtils.Smootherstep(loopObj.LazyJumpDistance, 0, OsuDifficultyHitObject.NORMALISED_RADIUS);
|
||||
|
||||
constantAngleCount += Math.Cos(3 * Math.Min(double.DegreesToRadians(30), Math.Min(angleDifference, angleDifferenceAlternating) * stackFactor)) * longIntervalFactor;
|
||||
}
|
||||
|
||||
currentTimeGap = current.StartTime - loopObj.StartTime;
|
||||
index++;
|
||||
|
||||
loopObjPrev2 = loopObjPrev1;
|
||||
loopObjPrev1 = loopObjPrev0;
|
||||
loopObjPrev0 = loopObj;
|
||||
}
|
||||
|
||||
return Math.Clamp(2 / constantAngleCount, 0.2, 1);
|
||||
}
|
||||
|
||||
// Returns a nerfing factor for when objects are very distant in time, affecting reading less.
|
||||
private static double getTimeNerfFactor(double deltaTime)
|
||||
{
|
||||
return Math.Clamp(2 - deltaTime / (reading_window_size / 2), 0, 1);
|
||||
}
|
||||
|
||||
private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.8, ms / 1000));
|
||||
}
|
||||
}
|
||||
+62
-32
@@ -8,15 +8,16 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
|
||||
{
|
||||
public static class RhythmEvaluator
|
||||
{
|
||||
private const int history_time_max = 5 * 1000; // 5 seconds
|
||||
private const int history_objects_max = 32;
|
||||
private const double rhythm_overall_multiplier = 1.0;
|
||||
private const double rhythm_ratio_multiplier = 15.0;
|
||||
private const double rhythm_overall_multiplier = 0.95;
|
||||
private const double rhythm_ratio_multiplier = 26.0;
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a rhythm multiplier for the difficulty of the tap associated with historic data of the current <see cref="OsuDifficultyHitObject"/>.
|
||||
@@ -26,11 +27,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
var currentOsuObject = (OsuDifficultyHitObject)current;
|
||||
|
||||
double rhythmComplexitySum = 0;
|
||||
|
||||
double deltaDifferenceEpsilon = ((OsuDifficultyHitObject)current).HitWindowGreat * 0.3;
|
||||
double deltaDifferenceEpsilon = ((OsuDifficultyHitObject)current).HitWindow(HitResult.Great) * 0.3;
|
||||
|
||||
var island = new Island(deltaDifferenceEpsilon);
|
||||
var previousIsland = new Island(deltaDifferenceEpsilon);
|
||||
@@ -57,6 +56,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
for (int i = rhythmStart; i > 0; i--)
|
||||
{
|
||||
OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)current.Previous(i - 1);
|
||||
if (currObj.BaseObject is Spinner)
|
||||
continue;
|
||||
|
||||
// scales note 0 to 1 from history to now
|
||||
double timeDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max;
|
||||
@@ -64,44 +65,56 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
|
||||
double currHistoricalDecay = Math.Min(noteDecay, timeDecay); // either we're limited by time or limited by object count.
|
||||
|
||||
// Use custom cap value to ensure that that at this point delta time is actually zero
|
||||
// Use custom cap value to ensure that at this point delta time is actually zero
|
||||
double currDelta = Math.Max(currObj.DeltaTime, 1e-7);
|
||||
double prevDelta = Math.Max(prevObj.DeltaTime, 1e-7);
|
||||
double lastDelta = Math.Max(lastObj.DeltaTime, 1e-7);
|
||||
|
||||
// Make sure to always have the current island initialised - if we don't do it here it will only initialise on the next rhythm change
|
||||
if (island.Delta == int.MaxValue)
|
||||
island = new Island((int)currDelta, deltaDifferenceEpsilon);
|
||||
|
||||
// calculate how much current delta difference deserves a rhythm bonus
|
||||
// this function is meant to reduce rhythm bonus for deltas that are multiples of each other (i.e 100 and 200)
|
||||
double deltaDifference = Math.Max(prevDelta, currDelta) / Math.Min(prevDelta, currDelta);
|
||||
|
||||
// Take only the fractional part of the value since we're only interested in punishing multiples
|
||||
double deltaDifferenceFraction = deltaDifference - Math.Truncate(deltaDifference);
|
||||
|
||||
double currRatio = 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, DifficultyCalculationUtils.SmoothstepBellCurve(deltaDifferenceFraction));
|
||||
|
||||
// reduce ratio bonus if delta difference is too big
|
||||
double differenceMultiplier = Math.Clamp(2.0 - deltaDifference / 8.0, 0.0, 1.0);
|
||||
|
||||
double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - deltaDifferenceEpsilon) / deltaDifferenceEpsilon);
|
||||
|
||||
double effectiveRatio = windowPenalty * currRatio * differenceMultiplier;
|
||||
double effectiveRatio = getEffectiveRatio(deltaDifference) * windowPenalty * differenceMultiplier;
|
||||
|
||||
// if previous object is a slider it might be easier to tap since you don't have to do a whole tapping motion
|
||||
// while a full deltatime might end up some weird ratio the "unpress->tap" motion might be simple
|
||||
// for example a slider-circle-circle pattern should be evaluated as a regular triple and not as a single->double
|
||||
if (prevObj.BaseObject is Slider)
|
||||
{
|
||||
double sliderLazyEndDelta = currObj.MinimumJumpTime;
|
||||
double sliderLazyDeltaDifference = Math.Max(sliderLazyEndDelta, currDelta) / Math.Min(sliderLazyEndDelta, currDelta);
|
||||
|
||||
double sliderRealEndDelta = currObj.LastObjectEndDeltaTime;
|
||||
double sliderRealDeltaDifference = Math.Max(sliderRealEndDelta, currDelta) / Math.Min(sliderRealEndDelta, currDelta);
|
||||
|
||||
double sliderEffectiveRatio = Math.Min(getEffectiveRatio(sliderLazyDeltaDifference), getEffectiveRatio(sliderRealDeltaDifference));
|
||||
effectiveRatio = Math.Min(sliderEffectiveRatio, effectiveRatio);
|
||||
}
|
||||
|
||||
bool isSpeedingUp = prevDelta > currDelta + deltaDifferenceEpsilon;
|
||||
|
||||
if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
|
||||
{
|
||||
// island is still progressing
|
||||
island.AddDelta((int)currDelta);
|
||||
}
|
||||
|
||||
if (firstDeltaSwitch)
|
||||
{
|
||||
if (Math.Abs(prevDelta - currDelta) < deltaDifferenceEpsilon)
|
||||
{
|
||||
// island is still progressing
|
||||
island.AddDelta((int)currDelta);
|
||||
}
|
||||
else
|
||||
if (Math.Abs(prevDelta - currDelta) > deltaDifferenceEpsilon)
|
||||
{
|
||||
// bpm change is into slider, this is easy acc window
|
||||
if (currObj.BaseObject is Slider)
|
||||
effectiveRatio *= 0.125;
|
||||
|
||||
// bpm change was from a slider, this is easier typically than circle -> circle
|
||||
// unintentional side effect is that bursts with kicksliders at the ends might have lower difficulty than bursts without sliders
|
||||
if (prevObj.BaseObject is Slider)
|
||||
effectiveRatio *= 0.3;
|
||||
effectiveRatio *= 0.5;
|
||||
|
||||
// repeated island polarity (2 -> 4, 3 -> 5)
|
||||
if (island.IsSimilarPolarity(previousIsland))
|
||||
@@ -116,6 +129,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (previousIsland.DeltaCount == island.DeltaCount)
|
||||
effectiveRatio *= 0.5;
|
||||
|
||||
if (isSpeedingUp)
|
||||
effectiveRatio *= 0.65;
|
||||
|
||||
var islandCount = islandCounts.FirstOrDefault(x => x.Island.Equals(island));
|
||||
|
||||
if (islandCount != default)
|
||||
@@ -134,7 +150,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
}
|
||||
else
|
||||
{
|
||||
islandCounts.Add((island, 1));
|
||||
if (island.DeltaCount > 0)
|
||||
{
|
||||
islandCounts.Add((island, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// scale down the difficulty if the object is doubletappable
|
||||
@@ -176,10 +195,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
prevObj = currObj;
|
||||
}
|
||||
|
||||
double rhythmDifficulty = Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though)
|
||||
rhythmDifficulty *= 1 - currentOsuObject.GetDoubletapness((OsuDifficultyHitObject)current.Next(0));
|
||||
// If the current island is long we don't want the sum to have as big of an effect
|
||||
rhythmComplexitySum *= DifficultyCalculationUtils.ReverseLerp(island.DeltaCount, 22, 3);
|
||||
|
||||
return rhythmDifficulty;
|
||||
return Math.Sqrt(4 + rhythmComplexitySum * rhythm_overall_multiplier) / 2.0; // produces multiplier that can be applied to strain. range [1, infinity) (not really though);
|
||||
}
|
||||
|
||||
private static double getEffectiveRatio(double deltaDifference)
|
||||
{
|
||||
// Take only the fractional part of the value since we're only interested in punishing multiples
|
||||
double deltaDifferenceFraction = deltaDifference - Math.Truncate(deltaDifference);
|
||||
|
||||
return 1.0 + rhythm_ratio_multiplier * Math.Min(0.5, DifficultyCalculationUtils.SmoothstepBellCurve(deltaDifferenceFraction));
|
||||
}
|
||||
|
||||
private class Island : IEquatable<Island>
|
||||
@@ -211,9 +238,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
|
||||
public bool IsSimilarPolarity(Island other)
|
||||
{
|
||||
// TODO: consider islands to be of similar polarity only if they're having the same average delta (we don't want to consider 3 singletaps similar to a triple)
|
||||
// naively adding delta check here breaks _a lot_ of maps because of the flawed ratio calculation
|
||||
return DeltaCount % 2 == other.DeltaCount % 2;
|
||||
// single delta islands shouldn't be compared
|
||||
if (DeltaCount <= 1 || other.DeltaCount <= 1)
|
||||
return false;
|
||||
|
||||
return Math.Abs(Delta - other.Delta) < deltaDifferenceEpsilon &&
|
||||
DeltaCount % 2 == other.DeltaCount % 2;
|
||||
}
|
||||
|
||||
public bool Equals(Island? other)
|
||||
+10
-29
@@ -2,47 +2,39 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed
|
||||
{
|
||||
public static class SpeedEvaluator
|
||||
{
|
||||
private const double single_spacing_threshold = OsuDifficultyHitObject.NORMALISED_DIAMETER * 1.25; // 1.25 circles distance between centers
|
||||
private const double min_speed_bonus = 200; // 200 BPM 1/4th
|
||||
private const double speed_balancing_factor = 40;
|
||||
private const double distance_multiplier = 0.8;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the difficulty of tapping the current object, based on:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>time between pressing the previous and current object,</description></item>
|
||||
/// <item><description>distance between those objects,</description></item>
|
||||
/// <item><description>and how easily they can be cheesed.</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current, IReadOnlyList<Mod> mods)
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
if (current.BaseObject is Spinner)
|
||||
return 0;
|
||||
|
||||
// derive strainTime for calculation
|
||||
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||
var osuPrevObj = current.Index > 0 ? (OsuDifficultyHitObject)current.Previous(0) : null;
|
||||
|
||||
double strainTime = osuCurrObj.AdjustedDeltaTime;
|
||||
double doubletapness = 1.0 - osuCurrObj.GetDoubletapness((OsuDifficultyHitObject?)osuCurrObj.Next(0));
|
||||
|
||||
// Cap deltatime to the OD 300 hitwindow.
|
||||
// 0.93 is derived from making sure 260bpm OD8 streams aren't nerfed harshly, whilst 0.92 limits the effect of the cap.
|
||||
strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindowGreat) / 0.93, 0.92, 1);
|
||||
strainTime /= Math.Clamp((strainTime / osuCurrObj.HitWindow(HitResult.Great)) / 0.93, 0.92, 1);
|
||||
|
||||
// speedBonus will be 0.0 for BPM < 200
|
||||
double speedBonus = 0.0;
|
||||
@@ -51,26 +43,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators
|
||||
if (DifficultyCalculationUtils.MillisecondsToBPM(strainTime) > min_speed_bonus)
|
||||
speedBonus = 0.75 * Math.Pow((DifficultyCalculationUtils.BPMToMilliseconds(min_speed_bonus) - strainTime) / speed_balancing_factor, 2);
|
||||
|
||||
double travelDistance = osuPrevObj?.TravelDistance ?? 0;
|
||||
double distance = travelDistance + osuCurrObj.MinimumJumpDistance;
|
||||
|
||||
// Cap distance at single_spacing_threshold
|
||||
distance = Math.Min(distance, single_spacing_threshold);
|
||||
|
||||
// Max distance bonus is 1 * `distance_multiplier` at single_spacing_threshold
|
||||
double distanceBonus = Math.Pow(distance / single_spacing_threshold, 3.95) * distance_multiplier;
|
||||
|
||||
// Apply reduced small circle bonus because flow aim difficulty on small circles doesn't scale as hard as jumps
|
||||
distanceBonus *= Math.Sqrt(osuCurrObj.SmallCircleBonus);
|
||||
|
||||
if (mods.OfType<OsuModAutopilot>().Any())
|
||||
distanceBonus = 0;
|
||||
|
||||
// Base difficulty with all bonuses
|
||||
double difficulty = (1 + speedBonus + distanceBonus) * 1000 / strainTime;
|
||||
double speedDifficulty = (1 + speedBonus) * 1000 / strainTime;
|
||||
|
||||
speedDifficulty *= highBpmBonus(osuCurrObj.AdjustedDeltaTime);
|
||||
|
||||
// Apply penalty if there's doubletappable doubles
|
||||
return difficulty * doubletapness;
|
||||
return speedDifficulty * doubletapness;
|
||||
}
|
||||
|
||||
private static double highBpmBonus(double ms) => 1 / (1 - Math.Pow(0.3, ms / 1000));
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
[JsonProperty("flashlight_difficulty")]
|
||||
public double FlashlightDifficulty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty corresponding to the reading skill.
|
||||
/// </summary>
|
||||
[JsonProperty("reading_difficulty")]
|
||||
public double ReadingDifficulty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Describes how much of <see cref="AimDifficulty"/> is contributed to by hitcircles or sliders.
|
||||
/// A value closer to 1.0 indicates most of <see cref="AimDifficulty"/> is contributed by hitcircles.
|
||||
@@ -75,6 +81,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
[JsonProperty("speed_difficult_strain_count")]
|
||||
public double SpeedDifficultStrainCount { get; set; }
|
||||
|
||||
[JsonProperty("reading_difficult_note_count")]
|
||||
public double ReadingDifficultNoteCount { get; set; }
|
||||
|
||||
[JsonProperty("nested_score_per_object")]
|
||||
public double NestedScorePerObject { get; set; }
|
||||
|
||||
@@ -84,11 +93,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
[JsonProperty("maximum_legacy_combo_score")]
|
||||
public double MaximumLegacyComboScore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap's drain rate. This doesn't scale with rate-adjusting mods.
|
||||
/// </summary>
|
||||
public double DrainRate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of hitcircles in the beatmap.
|
||||
/// </summary>
|
||||
@@ -111,6 +115,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
yield return (ATTRIB_ID_AIM, AimDifficulty);
|
||||
yield return (ATTRIB_ID_SPEED, SpeedDifficulty);
|
||||
yield return (ATTRIB_ID_READING, ReadingDifficulty);
|
||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||
|
||||
if (ShouldSerializeFlashlightDifficulty())
|
||||
@@ -127,6 +132,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
yield return (ATTRIB_ID_NESTED_SCORE_PER_OBJECT, NestedScorePerObject);
|
||||
yield return (ATTRIB_ID_LEGACY_SCORE_BASE_MULTIPLIER, LegacyScoreBaseMultiplier);
|
||||
yield return (ATTRIB_ID_MAXIMUM_LEGACY_COMBO_SCORE, MaximumLegacyComboScore);
|
||||
yield return (ATTRIB_ID_READING_DIFFICULT_NOTE_COUNT, ReadingDifficultNoteCount);
|
||||
}
|
||||
|
||||
public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
|
||||
@@ -135,6 +141,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
AimDifficulty = values[ATTRIB_ID_AIM];
|
||||
SpeedDifficulty = values[ATTRIB_ID_SPEED];
|
||||
ReadingDifficulty = values[ATTRIB_ID_READING];
|
||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||
FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT);
|
||||
SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR];
|
||||
@@ -147,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
NestedScorePerObject = values[ATTRIB_ID_NESTED_SCORE_PER_OBJECT];
|
||||
LegacyScoreBaseMultiplier = values[ATTRIB_ID_LEGACY_SCORE_BASE_MULTIPLIER];
|
||||
MaximumLegacyComboScore = values[ATTRIB_ID_MAXIMUM_LEGACY_COMBO_SCORE];
|
||||
DrainRate = onlineInfo.DrainRate;
|
||||
ReadingDifficultNoteCount = values[ATTRIB_ID_READING_DIFFICULT_NOTE_COUNT];
|
||||
HitCircleCount = onlineInfo.CircleCount;
|
||||
SliderCount = onlineInfo.SliderCount;
|
||||
SpinnerCount = onlineInfo.SpinnerCount;
|
||||
|
||||
@@ -8,6 +8,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
@@ -16,13 +17,12 @@ using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
private const double star_rating_multiplier = 0.0265;
|
||||
|
||||
public override int Version => 20251020;
|
||||
|
||||
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
|
||||
@@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return (79.5 - hitWindowGreat) / 6;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new OsuDifficultyAttributes { Mods = mods };
|
||||
@@ -55,24 +55,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
var aimWithoutSliders = skills.OfType<Aim>().Single(a => !a.IncludeSliders);
|
||||
var speed = skills.OfType<Speed>().Single();
|
||||
var flashlight = skills.OfType<Flashlight>().SingleOrDefault();
|
||||
var reading = skills.OfType<Reading>().Single();
|
||||
|
||||
double aimDifficultyValue = aim.DifficultyValue();
|
||||
double aimNoSlidersDifficultyValue = aimWithoutSliders.DifficultyValue();
|
||||
double speedDifficultyValue = speed.DifficultyValue();
|
||||
double readingDifficultyValue = reading.DifficultyValue();
|
||||
|
||||
double aimDifficultStrainCount = aim.CountTopWeightedStrains(aimDifficultyValue);
|
||||
double speedDifficultStrainCount = speed.CountTopWeightedObjectDifficulties(speedDifficultyValue);
|
||||
double readingDifficultNoteCount = reading.CountTopWeightedObjectDifficulties(readingDifficultyValue);
|
||||
|
||||
double speedNotes = speed.RelevantNoteCount();
|
||||
|
||||
double aimDifficultStrainCount = aim.CountTopWeightedStrains();
|
||||
double speedDifficultStrainCount = speed.CountTopWeightedStrains();
|
||||
|
||||
double aimNoSlidersTopWeightedSliderCount = aimWithoutSliders.CountTopWeightedSliders();
|
||||
double aimNoSlidersDifficultStrainCount = aimWithoutSliders.CountTopWeightedStrains();
|
||||
double aimNoSlidersTopWeightedSliderCount = aimWithoutSliders.CountTopWeightedSliders(aimNoSlidersDifficultyValue);
|
||||
double aimNoSlidersDifficultStrainCount = aimWithoutSliders.CountTopWeightedStrains(aimNoSlidersDifficultyValue);
|
||||
|
||||
double aimTopWeightedSliderFactor = aimNoSlidersTopWeightedSliderCount / Math.Max(1, aimNoSlidersDifficultStrainCount - aimNoSlidersTopWeightedSliderCount);
|
||||
|
||||
double speedTopWeightedSliderCount = speed.CountTopWeightedSliders();
|
||||
double speedTopWeightedSliderCount = speed.CountTopWeightedSliders(speedDifficultyValue);
|
||||
double speedTopWeightedSliderFactor = speedTopWeightedSliderCount / Math.Max(1, speedDifficultStrainCount - speedTopWeightedSliderCount);
|
||||
|
||||
double difficultSliders = aim.GetDifficultSliders();
|
||||
|
||||
double approachRate = CalculateRateAdjustedApproachRate(beatmap.Difficulty.ApproachRate, clockRate);
|
||||
double overallDifficulty = CalculateRateAdjustedOverallDifficulty(beatmap.Difficulty.OverallDifficulty, clockRate);
|
||||
double overallDifficulty = CalculateRateAdjustedOverallDifficulty(beatmap.Difficulty.OverallDifficulty, ModUtils.CalculateRateWithMods(mods));
|
||||
|
||||
int hitCircleCount = beatmap.HitObjects.Count(h => h is HitCircle);
|
||||
int sliderCount = beatmap.HitObjects.Count(h => h is Slider);
|
||||
@@ -80,19 +86,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
int totalHits = beatmap.HitObjects.Count;
|
||||
|
||||
double drainRate = beatmap.Difficulty.DrainRate;
|
||||
double sliderFactor = aimDifficultyValue > 0
|
||||
? OsuRatingCalculator.CalculateDifficultyRating(aimNoSlidersDifficultyValue) / OsuRatingCalculator.CalculateDifficultyRating(aimDifficultyValue)
|
||||
: 1;
|
||||
|
||||
double aimDifficultyValue = aim.DifficultyValue();
|
||||
double aimNoSlidersDifficultyValue = aimWithoutSliders.DifficultyValue();
|
||||
double speedDifficultyValue = speed.DifficultyValue();
|
||||
|
||||
double mechanicalDifficultyRating = calculateMechanicalDifficultyRating(aimDifficultyValue, speedDifficultyValue);
|
||||
double sliderFactor = aimDifficultyValue > 0 ? OsuRatingCalculator.CalculateDifficultyRating(aimNoSlidersDifficultyValue) / OsuRatingCalculator.CalculateDifficultyRating(aimDifficultyValue) : 1;
|
||||
|
||||
var osuRatingCalculator = new OsuRatingCalculator(mods, totalHits, approachRate, overallDifficulty, mechanicalDifficultyRating, sliderFactor);
|
||||
var osuRatingCalculator = new OsuRatingCalculator(totalHits, overallDifficulty);
|
||||
|
||||
double aimRating = osuRatingCalculator.ComputeAimRating(aimDifficultyValue);
|
||||
double speedRating = osuRatingCalculator.ComputeSpeedRating(speedDifficultyValue);
|
||||
double readingRating = osuRatingCalculator.ComputeReadingRating(readingDifficultyValue);
|
||||
|
||||
double flashlightRating = 0.0;
|
||||
|
||||
@@ -100,21 +102,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
flashlightRating = osuRatingCalculator.ComputeFlashlightRating(flashlight.DifficultyValue());
|
||||
|
||||
double sliderNestedScorePerObject = LegacyScoreUtils.CalculateNestedScorePerObject(beatmap, totalHits);
|
||||
double legacyScoreBaseMultiplier = LegacyScoreUtils.CalculateDifficultyPeppyStars(beatmap);
|
||||
double legacyScoreBaseMultiplier = LegacyScoreUtils.CalculateDifficultyPeppyStars(WorkingBeatmap.Beatmap);
|
||||
|
||||
var simulator = new OsuLegacyScoreSimulator();
|
||||
var scoreAttributes = simulator.Simulate(WorkingBeatmap, beatmap);
|
||||
|
||||
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||
double baseAimPerformance = OsuPerformanceCalculator.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = HarmonicSkill.DifficultyToPerformance(speedRating);
|
||||
double baseReadingPerformance = HarmonicSkill.DifficultyToPerformance(readingRating);
|
||||
double baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
double baseCognitionPerformance = SumCognitionDifficulty(baseReadingPerformance, baseFlashlightPerformance);
|
||||
|
||||
double basePerformance =
|
||||
Math.Pow(
|
||||
Math.Pow(baseAimPerformance, 1.1) +
|
||||
Math.Pow(baseSpeedPerformance, 1.1) +
|
||||
Math.Pow(baseFlashlightPerformance, 1.1), 1.0 / 1.1
|
||||
);
|
||||
double basePerformance = DifficultyCalculationUtils.Norm(OsuPerformanceCalculator.PERFORMANCE_NORM_EXPONENT, baseAimPerformance, baseSpeedPerformance, baseCognitionPerformance);
|
||||
|
||||
double starRating = calculateStarRating(basePerformance);
|
||||
|
||||
@@ -127,12 +126,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
SpeedDifficulty = speedRating,
|
||||
SpeedNoteCount = speedNotes,
|
||||
FlashlightDifficulty = flashlightRating,
|
||||
ReadingDifficulty = readingRating,
|
||||
SliderFactor = sliderFactor,
|
||||
AimDifficultStrainCount = aimDifficultStrainCount,
|
||||
SpeedDifficultStrainCount = speedDifficultStrainCount,
|
||||
ReadingDifficultNoteCount = readingDifficultNoteCount,
|
||||
AimTopWeightedSliderFactor = aimTopWeightedSliderFactor,
|
||||
SpeedTopWeightedSliderFactor = speedTopWeightedSliderFactor,
|
||||
DrainRate = drainRate,
|
||||
MaxCombo = beatmap.GetMaxCombo(),
|
||||
HitCircleCount = hitCircleCount,
|
||||
SliderCount = sliderCount,
|
||||
@@ -145,28 +145,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return attributes;
|
||||
}
|
||||
|
||||
private double calculateMechanicalDifficultyRating(double aimDifficultyValue, double speedDifficultyValue)
|
||||
public static double SumCognitionDifficulty(double reading, double flashlight)
|
||||
{
|
||||
double aimValue = OsuStrainSkill.DifficultyToPerformance(OsuRatingCalculator.CalculateDifficultyRating(aimDifficultyValue));
|
||||
double speedValue = OsuStrainSkill.DifficultyToPerformance(OsuRatingCalculator.CalculateDifficultyRating(speedDifficultyValue));
|
||||
if (reading <= 0)
|
||||
return flashlight;
|
||||
|
||||
double totalValue = Math.Pow(Math.Pow(aimValue, 1.1) + Math.Pow(speedValue, 1.1), 1 / 1.1);
|
||||
if (flashlight <= 0)
|
||||
return reading;
|
||||
|
||||
return calculateStarRating(totalValue);
|
||||
// Nerf flashlight value in cognition sum when reading is greater than flashlight
|
||||
return DifficultyCalculationUtils.Norm(OsuPerformanceCalculator.PERFORMANCE_NORM_EXPONENT, reading, flashlight * Math.Clamp(flashlight / reading, 0.25, 1.0));
|
||||
}
|
||||
|
||||
private double calculateStarRating(double basePerformance)
|
||||
{
|
||||
if (basePerformance <= 0.00001)
|
||||
return 0;
|
||||
|
||||
return Math.Cbrt(OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER) * star_rating_multiplier * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4);
|
||||
return Math.Cbrt(basePerformance * OsuPerformanceCalculator.PERFORMANCE_BASE_MULTIPLIER);
|
||||
}
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
List<DifficultyHitObject> objects = new List<DifficultyHitObject>();
|
||||
|
||||
double clockRate = ModUtils.CalculateRateWithMods(mods);
|
||||
|
||||
// The first jump is formed by the first two hitobjects of the map.
|
||||
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
|
||||
for (int i = 1; i < beatmap.HitObjects.Count; i++)
|
||||
@@ -177,13 +178,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return objects;
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
var skills = new List<Skill>
|
||||
{
|
||||
new Aim(mods, true),
|
||||
new Aim(mods, false),
|
||||
new Speed(mods)
|
||||
new Speed(mods),
|
||||
new Reading(mods)
|
||||
};
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
|
||||
@@ -115,9 +115,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
double missCount = 0;
|
||||
|
||||
// If sliders in the map are hard - it's likely for player to drop sliderends
|
||||
// If map has easy sliders - it's more likely for player to sliderbreak
|
||||
double likelyMissedSliderendPortion = 0.04 + 0.06 * Math.Pow(Math.Min(attributes.AimTopWeightedSliderFactor, 1), 2);
|
||||
|
||||
// Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it
|
||||
// In classic scores we can't know the amount of dropped sliders so we estimate to 10% of all sliders on the map
|
||||
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
|
||||
// In classic scores we can't know the amount of dropped sliders so we estimate it
|
||||
double fullComboThreshold = attributes.MaxCombo - Math.Min(4 + likelyMissedSliderendPortion * attributes.SliderCount, attributes.SliderCount);
|
||||
|
||||
if (score.MaxCombo < fullComboThreshold)
|
||||
missCount = Math.Pow(fullComboThreshold / Math.Max(1.0, score.MaxCombo), 2.5);
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
[JsonProperty("flashlight")]
|
||||
public double Flashlight { get; set; }
|
||||
|
||||
[JsonProperty("reading")]
|
||||
public double Reading { get; set; }
|
||||
|
||||
[JsonProperty("effective_miss_count")]
|
||||
public double EffectiveMissCount { get; set; }
|
||||
|
||||
@@ -48,6 +51,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
yield return new PerformanceDisplayAttribute(nameof(Speed), "Speed", Speed);
|
||||
yield return new PerformanceDisplayAttribute(nameof(Accuracy), "Accuracy", Accuracy);
|
||||
yield return new PerformanceDisplayAttribute(nameof(Flashlight), "Flashlight Bonus", Flashlight);
|
||||
yield return new PerformanceDisplayAttribute(nameof(Reading), "Reading", Reading);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@@ -19,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
public const double PERFORMANCE_BASE_MULTIPLIER = 1.14; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
public const double PERFORMANCE_BASE_MULTIPLIER = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things.
|
||||
public const double PERFORMANCE_NORM_EXPONENT = 1.1;
|
||||
|
||||
private bool usingClassicSliderAccuracy;
|
||||
private bool usingScoreV2;
|
||||
@@ -50,14 +52,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
private double greatHitWindow;
|
||||
private double okHitWindow;
|
||||
private double mehHitWindow;
|
||||
|
||||
private double overallDifficulty;
|
||||
private double approachRate;
|
||||
private double drainRate;
|
||||
|
||||
private double? speedDeviation;
|
||||
|
||||
private double aimEstimatedSliderBreaks;
|
||||
private double speedEstimatedSliderBreaks;
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => 4.0 * Math.Pow(difficulty, 3.0);
|
||||
|
||||
public OsuPerformanceCalculator()
|
||||
: base(new OsuRuleset())
|
||||
{
|
||||
@@ -95,11 +101,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
approachRate = OsuDifficultyCalculator.CalculateRateAdjustedApproachRate(difficulty.ApproachRate, clockRate);
|
||||
overallDifficulty = OsuDifficultyCalculator.CalculateRateAdjustedOverallDifficulty(difficulty.OverallDifficulty, clockRate);
|
||||
drainRate = difficulty.DrainRate;
|
||||
|
||||
double comboBasedEstimatedMissCount = calculateComboBasedEstimatedMissCount(osuAttributes);
|
||||
double? scoreBasedEstimatedMissCount = null;
|
||||
|
||||
if (usingClassicSliderAccuracy && score.LegacyTotalScore != null)
|
||||
if (usingClassicSliderAccuracy && !usingScoreV2 && score.LegacyTotalScore != null)
|
||||
{
|
||||
var legacyScoreMissCalculator = new OsuLegacyScoreMissCalculator(score, osuAttributes);
|
||||
scoreBasedEstimatedMissCount = legacyScoreMissCalculator.Calculate();
|
||||
@@ -115,6 +122,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
effectiveMissCount = Math.Max(countMiss, effectiveMissCount);
|
||||
effectiveMissCount = Math.Min(totalHits, effectiveMissCount);
|
||||
|
||||
if (effectiveMissCount > 0)
|
||||
{
|
||||
aimEstimatedSliderBreaks = calculateEstimatedSliderBreaks(osuAttributes.AimTopWeightedSliderFactor, osuAttributes);
|
||||
speedEstimatedSliderBreaks = calculateEstimatedSliderBreaks(osuAttributes.SpeedTopWeightedSliderFactor, osuAttributes);
|
||||
}
|
||||
|
||||
double multiplier = PERFORMANCE_BASE_MULTIPLIER;
|
||||
|
||||
if (score.Mods.Any(m => m is OsuModNoFail))
|
||||
@@ -140,15 +153,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double aimValue = computeAimValue(score, osuAttributes);
|
||||
double speedValue = computeSpeedValue(score, osuAttributes);
|
||||
double accuracyValue = computeAccuracyValue(score, osuAttributes);
|
||||
double flashlightValue = computeFlashlightValue(score, osuAttributes);
|
||||
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(aimValue, 1.1) +
|
||||
Math.Pow(speedValue, 1.1) +
|
||||
Math.Pow(accuracyValue, 1.1) +
|
||||
Math.Pow(flashlightValue, 1.1), 1.0 / 1.1
|
||||
) * multiplier;
|
||||
double readingValue = computeReadingValue(osuAttributes);
|
||||
double flashlightValue = computeFlashlightValue(score, osuAttributes);
|
||||
double cognitionValue = OsuDifficultyCalculator.SumCognitionDifficulty(readingValue, flashlightValue);
|
||||
|
||||
double totalValue = DifficultyCalculationUtils.Norm(PERFORMANCE_NORM_EXPONENT, aimValue, speedValue, accuracyValue, cognitionValue) * multiplier;
|
||||
|
||||
return new OsuPerformanceAttributes
|
||||
{
|
||||
@@ -156,6 +166,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
Speed = speedValue,
|
||||
Accuracy = accuracyValue,
|
||||
Flashlight = flashlightValue,
|
||||
Reading = readingValue,
|
||||
EffectiveMissCount = effectiveMissCount,
|
||||
ComboBasedEstimatedMissCount = comboBasedEstimatedMissCount,
|
||||
ScoreBasedEstimatedMissCount = scoreBasedEstimatedMissCount,
|
||||
@@ -194,16 +205,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
aimDifficulty *= sliderNerfFactor;
|
||||
}
|
||||
|
||||
double aimValue = OsuStrainSkill.DifficultyToPerformance(aimDifficulty);
|
||||
double aimValue = DifficultyToPerformance(aimDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
double lengthBonus = 0.95 + 0.35 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
aimValue *= lengthBonus;
|
||||
|
||||
if (effectiveMissCount > 0)
|
||||
{
|
||||
aimEstimatedSliderBreaks = calculateEstimatedSliderBreaks(attributes.AimTopWeightedSliderFactor, attributes);
|
||||
|
||||
double relevantMissCount = Math.Min(effectiveMissCount + aimEstimatedSliderBreaks, totalImperfectHits + countSliderTickMiss);
|
||||
|
||||
aimValue *= calculateMissPenalty(relevantMissCount, attributes.AimDifficultStrainCount);
|
||||
@@ -211,10 +220,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
// TC bonuses are excluded when blinds is present as the increased visual difficulty is unimportant when notes cannot be seen.
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * attributes.DrainRate * attributes.DrainRate);
|
||||
aimValue *= 1.3 + (totalHits * (0.0016 / (1 + 2 * effectiveMissCount)) * Math.Pow(accuracy, 16)) * (1 - 0.003 * drainRate * drainRate);
|
||||
else if (score.Mods.Any(m => m is OsuModTraceable))
|
||||
{
|
||||
aimValue *= 1.0 + OsuRatingCalculator.CalculateVisibilityBonus(score.Mods, approachRate, sliderFactor: attributes.SliderFactor);
|
||||
aimValue *= 1.0 + calculateTraceableBonus(attributes.SliderFactor);
|
||||
}
|
||||
|
||||
aimValue *= accuracy;
|
||||
@@ -227,44 +236,33 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (score.Mods.Any(h => h is OsuModRelax) || speedDeviation == null)
|
||||
return 0.0;
|
||||
|
||||
double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
speedValue *= lengthBonus;
|
||||
double speedValue = HarmonicSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
if (effectiveMissCount > 0)
|
||||
{
|
||||
speedEstimatedSliderBreaks = calculateEstimatedSliderBreaks(attributes.SpeedTopWeightedSliderFactor, attributes);
|
||||
|
||||
double relevantMissCount = Math.Min(effectiveMissCount + speedEstimatedSliderBreaks, totalImperfectHits + countSliderTickMiss);
|
||||
|
||||
speedValue *= calculateMissPenalty(relevantMissCount, attributes.SpeedDifficultStrainCount);
|
||||
}
|
||||
|
||||
// TC bonuses are excluded when blinds is present as the increased visual difficulty is unimportant when notes cannot be seen.
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
{
|
||||
// Increasing the speed value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||
speedValue *= 1.12;
|
||||
}
|
||||
else if (score.Mods.Any(m => m is OsuModTraceable))
|
||||
{
|
||||
speedValue *= 1.0 + OsuRatingCalculator.CalculateVisibilityBonus(score.Mods, approachRate);
|
||||
}
|
||||
|
||||
double speedHighDeviationMultiplier = calculateSpeedHighDeviationNerf(attributes);
|
||||
speedValue *= speedHighDeviationMultiplier;
|
||||
|
||||
// Calculate accuracy assuming the worst case scenario
|
||||
double relevantTotalDiff = Math.Max(0, totalHits - attributes.SpeedNoteCount);
|
||||
double relevantCountGreat = Math.Max(0, countGreat - relevantTotalDiff);
|
||||
double relevantCountOk = Math.Max(0, countOk - Math.Max(0, relevantTotalDiff - countGreat));
|
||||
double relevantCountMeh = Math.Max(0, countMeh - Math.Max(0, relevantTotalDiff - countGreat - countOk));
|
||||
double relevantAccuracy = attributes.SpeedNoteCount == 0 ? 0 : (relevantCountGreat * 6.0 + relevantCountOk * 2.0 + relevantCountMeh) / (attributes.SpeedNoteCount * 6.0);
|
||||
// An effective hit window is created based on the speed SR. The higher the speed difficulty, the shorter the hit window.
|
||||
// For example, a speed SR of 4.0 leads to an effective hit window of 20ms, which is OD 10.
|
||||
double effectiveHitWindow = 20 * Math.Pow(4 / attributes.SpeedDifficulty, 0.35);
|
||||
|
||||
// Scale the speed value with accuracy and OD.
|
||||
speedValue *= Math.Pow((accuracy + relevantAccuracy) / 2.0, (14.5 - overallDifficulty) / 2);
|
||||
// Find the proportion of 300s on speed notes assuming the hit window was the effective hit window.
|
||||
double effectiveAccuracy = DifficultyCalculationUtils.Erf(effectiveHitWindow / (double)speedDeviation);
|
||||
|
||||
// Scale speed value by normalized accuracy.
|
||||
speedValue *= Math.Pow(effectiveAccuracy, 2);
|
||||
|
||||
return speedValue;
|
||||
}
|
||||
@@ -294,20 +292,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
double accuracyValue = Math.Pow(1.52163, overallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer.
|
||||
accuracyValue *= Math.Min(1.15, Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3));
|
||||
accuracyValue *= amountHitObjectsWithAccuracy < 1000
|
||||
? Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.3)
|
||||
: Math.Pow(amountHitObjectsWithAccuracy / 1000.0, 0.1);
|
||||
|
||||
// Increasing the accuracy value by object count for Blinds isn't ideal, so the minimum buff is given.
|
||||
if (score.Mods.Any(m => m is OsuModBlinds))
|
||||
accuracyValue *= 1.14;
|
||||
else if (score.Mods.Any(m => m is OsuModHidden || m is OsuModTraceable))
|
||||
else if (score.Mods.Any(m => m is OsuModTraceable))
|
||||
{
|
||||
// Decrease bonus for AR > 10
|
||||
accuracyValue *= 1 + 0.08 * DifficultyCalculationUtils.ReverseLerp(approachRate, 11.5, 10);
|
||||
}
|
||||
|
||||
if (score.Mods.Any(m => m is OsuModFlashlight))
|
||||
accuracyValue *= 1.02;
|
||||
|
||||
return accuracyValue;
|
||||
}
|
||||
|
||||
@@ -330,6 +327,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return flashlightValue;
|
||||
}
|
||||
|
||||
private double computeReadingValue(OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double readingValue = HarmonicSkill.DifficultyToPerformance(attributes.ReadingDifficulty);
|
||||
|
||||
if (effectiveMissCount > 0)
|
||||
readingValue *= calculateMissPenalty(effectiveMissCount + aimEstimatedSliderBreaks, attributes.ReadingDifficultNoteCount);
|
||||
|
||||
// Scale the reading value with accuracy _harshly_.
|
||||
readingValue *= Math.Pow(accuracy, 3);
|
||||
|
||||
return readingValue;
|
||||
}
|
||||
|
||||
private double calculateComboBasedEstimatedMissCount(OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (attributes.SliderCount <= 0)
|
||||
@@ -339,9 +349,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
if (usingClassicSliderAccuracy)
|
||||
{
|
||||
// If sliders in the map are hard - it's likely for player to drop sliderends
|
||||
// If map has easy sliders - it's more likely for player to sliderbreak
|
||||
double likelyMissedSliderendPortion = 0.04 + 0.06 * Math.Pow(Math.Min(attributes.AimTopWeightedSliderFactor, 1), 2);
|
||||
|
||||
// Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it
|
||||
// In classic scores we can't know the amount of dropped sliders so we estimate to 10% of all sliders on the map
|
||||
double fullComboThreshold = attributes.MaxCombo - 0.1 * attributes.SliderCount;
|
||||
// In classic scores we can't know the amount of dropped sliders so we estimate it
|
||||
double fullComboThreshold = attributes.MaxCombo - Math.Min(4 + likelyMissedSliderendPortion * attributes.SliderCount, attributes.SliderCount);
|
||||
|
||||
if (scoreMaxCombo < fullComboThreshold)
|
||||
missCount = fullComboThreshold / Math.Max(1.0, scoreMaxCombo);
|
||||
@@ -376,19 +390,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double calculateEstimatedSliderBreaks(double topWeightedSliderFactor, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
if (!usingClassicSliderAccuracy || countOk == 0)
|
||||
int nonMissMistakes = countOk + countMeh;
|
||||
|
||||
if (!usingClassicSliderAccuracy || nonMissMistakes == 0)
|
||||
return 0;
|
||||
|
||||
double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo;
|
||||
double estimatedSliderBreaks = Math.Min(countOk, effectiveMissCount * topWeightedSliderFactor);
|
||||
double estimatedSliderBreaks = Math.Min(nonMissMistakes, effectiveMissCount * topWeightedSliderFactor);
|
||||
|
||||
// Scores with more Oks are more likely to have slider breaks.
|
||||
double okAdjustment = ((countOk - estimatedSliderBreaks) + 0.5) / countOk;
|
||||
// Scores with more Oks and Mehs are more likely to have slider breaks.
|
||||
// We add an arbitrary value to both sides of the division to make it more stable on extreme ends.
|
||||
double nonMissMistakeAdjustment = (nonMissMistakes - estimatedSliderBreaks + 4.5) / (nonMissMistakes + 4);
|
||||
|
||||
// There is a low probability of extra slider breaks on effective miss counts close to 1, as score based calculations are good at indicating if only a single break occurred.
|
||||
estimatedSliderBreaks *= DifficultyCalculationUtils.Smoothstep(effectiveMissCount, 1, 2);
|
||||
|
||||
return estimatedSliderBreaks * okAdjustment * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15);
|
||||
return estimatedSliderBreaks * nonMissMistakeAdjustment * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -470,7 +487,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (speedDeviation == null)
|
||||
return 0;
|
||||
|
||||
double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
double speedValue = HarmonicSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
// Decides a point where the PP value achieved compared to the speed deviation is assumed to be tapped improperly. Any PP above this point is considered "excess" speed difficulty.
|
||||
// This is used to cause PP above the cutoff to scale logarithmically towards the original speed value thus nerfing the value.
|
||||
@@ -489,10 +506,34 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return adjustedSpeedValue / speedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a visibility bonus that is applicable to Traceable.
|
||||
/// </summary>
|
||||
private double calculateTraceableBonus(double sliderFactor = 1)
|
||||
{
|
||||
// We want to reward slider aim less, more so at lower AR
|
||||
double highApproachRateSliderVisibilityFactor = 0.5 + (Math.Pow(sliderFactor, 6) / 2);
|
||||
double lowApproachRateSliderVisibilityFactor = Math.Pow(sliderFactor, 6);
|
||||
|
||||
// Start from normal curve, rewarding lower AR up to AR7
|
||||
double traceableBonus = 0.0275;
|
||||
traceableBonus += 0.025 * (12.0 - Math.Max(approachRate, 7)) * highApproachRateSliderVisibilityFactor;
|
||||
|
||||
// For AR up to 0 - reduce reward for very low ARs when object is visible
|
||||
if (approachRate < 7)
|
||||
traceableBonus += 0.025 * (7.0 - Math.Max(approachRate, 0)) * lowApproachRateSliderVisibilityFactor;
|
||||
|
||||
// Starting from AR0 - cap values so they won't grow to infinity
|
||||
if (approachRate < 0)
|
||||
traceableBonus += 0.025 * (1 - Math.Pow(1.5, approachRate)) * lowApproachRateSliderVisibilityFactor;
|
||||
|
||||
return traceableBonus;
|
||||
}
|
||||
|
||||
// Miss penalty assumes that a player will miss on the hardest parts of a map,
|
||||
// so we use the amount of relatively difficult sections to adjust miss penalty
|
||||
// to make it more punishing on maps with lower amount of hard sections.
|
||||
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.96 / ((missCount / (4 * Math.Pow(Math.Log(difficultStrainCount), 0.94))) + 1);
|
||||
private double calculateMissPenalty(double missCount, double difficultStrainCount) => 0.93 / (missCount / (4 * Math.Log(difficultStrainCount)) + 1);
|
||||
private double getComboScalingFactor(OsuDifficultyAttributes attributes) => attributes.MaxCombo <= 0 ? 1.0 : Math.Min(Math.Pow(scoreMaxCombo, 0.8) / Math.Pow(attributes.MaxCombo, 0.8), 1.0);
|
||||
|
||||
private int totalHits => countGreat + countOk + countMeh + countMiss;
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
@@ -13,64 +9,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
private const double difficulty_multiplier = 0.0675;
|
||||
|
||||
private readonly Mod[] mods;
|
||||
private readonly int totalHits;
|
||||
private readonly double approachRate;
|
||||
private readonly double overallDifficulty;
|
||||
private readonly double mechanicalDifficultyRating;
|
||||
private readonly double sliderFactor;
|
||||
|
||||
public OsuRatingCalculator(Mod[] mods, int totalHits, double approachRate, double overallDifficulty, double mechanicalDifficultyRating, double sliderFactor)
|
||||
public OsuRatingCalculator(int totalHits, double overallDifficulty)
|
||||
{
|
||||
this.mods = mods;
|
||||
this.totalHits = totalHits;
|
||||
this.approachRate = approachRate;
|
||||
this.overallDifficulty = overallDifficulty;
|
||||
this.mechanicalDifficultyRating = mechanicalDifficultyRating;
|
||||
this.sliderFactor = sliderFactor;
|
||||
}
|
||||
|
||||
public double ComputeAimRating(double aimDifficultyValue)
|
||||
{
|
||||
if (mods.Any(m => m is OsuModAutopilot))
|
||||
return 0;
|
||||
|
||||
double aimRating = CalculateDifficultyRating(aimDifficultyValue);
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
aimRating = Math.Pow(aimRating, 0.8);
|
||||
|
||||
if (mods.Any(m => m is OsuModRelax))
|
||||
aimRating *= 0.9;
|
||||
|
||||
if (mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
float magnetisedStrength = mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
aimRating *= 1.0 - magnetisedStrength;
|
||||
}
|
||||
double aimRating = Math.Pow(aimDifficultyValue, 0.63) * 0.02275;
|
||||
|
||||
double ratingMultiplier = 1.0;
|
||||
|
||||
double approachRateLengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (approachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (approachRate - 10.33);
|
||||
else if (approachRate < 8.0)
|
||||
approachRateFactor = 0.05 * (8.0 - approachRate);
|
||||
|
||||
if (mods.Any(h => h is OsuModRelax))
|
||||
approachRateFactor = 0.0;
|
||||
|
||||
ratingMultiplier += approachRateFactor * approachRateLengthBonus; // Buff for longer maps with high AR.
|
||||
|
||||
if (mods.Any(m => m is OsuModHidden))
|
||||
{
|
||||
double visibilityFactor = calculateAimVisibilityFactor(approachRate);
|
||||
ratingMultiplier += CalculateVisibilityBonus(mods, approachRate, visibilityFactor, sliderFactor);
|
||||
}
|
||||
|
||||
// It is important to consider accuracy difficulty when scaling with accuracy.
|
||||
ratingMultiplier *= 0.98 + Math.Pow(Math.Max(0, overallDifficulty), 2) / 2500;
|
||||
|
||||
@@ -79,73 +32,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
public double ComputeSpeedRating(double speedDifficultyValue)
|
||||
{
|
||||
if (mods.Any(m => m is OsuModRelax))
|
||||
return 0;
|
||||
return CalculateDifficultyRating(speedDifficultyValue);
|
||||
}
|
||||
|
||||
double speedRating = CalculateDifficultyRating(speedDifficultyValue);
|
||||
|
||||
if (mods.Any(m => m is OsuModAutopilot))
|
||||
speedRating *= 0.5;
|
||||
|
||||
if (mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
// reduce speed rating because of the speed distance scaling, with maximum reduction being 0.7x
|
||||
float magnetisedStrength = mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
speedRating *= 1.0 - magnetisedStrength * 0.3;
|
||||
}
|
||||
public double ComputeReadingRating(double readingDifficultyValue)
|
||||
{
|
||||
double readingRating = CalculateDifficultyRating(readingDifficultyValue);
|
||||
|
||||
double ratingMultiplier = 1.0;
|
||||
|
||||
double approachRateLengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
ratingMultiplier *= 0.75 + Math.Pow(Math.Max(0, overallDifficulty), 2.2) / 800;
|
||||
|
||||
double approachRateFactor = 0.0;
|
||||
if (approachRate > 10.33)
|
||||
approachRateFactor = 0.3 * (approachRate - 10.33);
|
||||
|
||||
if (mods.Any(m => m is OsuModAutopilot))
|
||||
approachRateFactor = 0.0;
|
||||
|
||||
ratingMultiplier += approachRateFactor * approachRateLengthBonus; // Buff for longer maps with high AR.
|
||||
|
||||
if (mods.Any(m => m is OsuModHidden))
|
||||
{
|
||||
double visibilityFactor = calculateSpeedVisibilityFactor(approachRate);
|
||||
ratingMultiplier += CalculateVisibilityBonus(mods, approachRate, visibilityFactor);
|
||||
}
|
||||
|
||||
ratingMultiplier *= 0.95 + Math.Pow(Math.Max(0, overallDifficulty), 2) / 750;
|
||||
|
||||
return speedRating * Math.Cbrt(ratingMultiplier);
|
||||
return readingRating * Math.Cbrt(ratingMultiplier);
|
||||
}
|
||||
|
||||
public double ComputeFlashlightRating(double flashlightDifficultyValue)
|
||||
{
|
||||
if (!mods.Any(m => m is OsuModFlashlight))
|
||||
return 0;
|
||||
|
||||
double flashlightRating = CalculateDifficultyRating(flashlightDifficultyValue);
|
||||
|
||||
if (mods.Any(m => m is OsuModTouchDevice))
|
||||
flashlightRating = Math.Pow(flashlightRating, 0.8);
|
||||
|
||||
if (mods.Any(m => m is OsuModRelax))
|
||||
flashlightRating *= 0.7;
|
||||
else if (mods.Any(m => m is OsuModAutopilot))
|
||||
flashlightRating *= 0.4;
|
||||
|
||||
if (mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
float magnetisedStrength = mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
flashlightRating *= 1.0 - magnetisedStrength;
|
||||
}
|
||||
|
||||
if (mods.Any(m => m is OsuModDeflate))
|
||||
{
|
||||
float deflateInitialScale = mods.OfType<OsuModDeflate>().First().StartScale.Value;
|
||||
flashlightRating *= Math.Clamp(DifficultyCalculationUtils.ReverseLerp(deflateInitialScale, 11, 1), 0.1, 1);
|
||||
}
|
||||
|
||||
double ratingMultiplier = 1.0;
|
||||
|
||||
// Account for shorter maps having a higher ratio of 0 combo/100 combo flashlight radius.
|
||||
@@ -158,56 +62,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
return flashlightRating * Math.Sqrt(ratingMultiplier);
|
||||
}
|
||||
|
||||
private double calculateAimVisibilityFactor(double approachRate)
|
||||
{
|
||||
const double ar_factor_end_point = 11.5;
|
||||
|
||||
double mechanicalDifficultyFactor = DifficultyCalculationUtils.ReverseLerp(mechanicalDifficultyRating, 5, 10);
|
||||
double arFactorStartingPoint = double.Lerp(9, 10.33, mechanicalDifficultyFactor);
|
||||
|
||||
return DifficultyCalculationUtils.ReverseLerp(approachRate, ar_factor_end_point, arFactorStartingPoint);
|
||||
}
|
||||
|
||||
private double calculateSpeedVisibilityFactor(double approachRate)
|
||||
{
|
||||
const double ar_factor_end_point = 11.5;
|
||||
|
||||
double mechanicalDifficultyFactor = DifficultyCalculationUtils.ReverseLerp(mechanicalDifficultyRating, 5, 10);
|
||||
double arFactorStartingPoint = double.Lerp(10, 10.33, mechanicalDifficultyFactor);
|
||||
|
||||
return DifficultyCalculationUtils.ReverseLerp(approachRate, ar_factor_end_point, arFactorStartingPoint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates a visibility bonus that is applicable to Hidden and Traceable.
|
||||
/// </summary>
|
||||
public static double CalculateVisibilityBonus(Mod[] mods, double approachRate, double visibilityFactor = 1, double sliderFactor = 1)
|
||||
{
|
||||
// NOTE: TC's effect is only noticeable in performance calculations until lazer mods are accounted for server-side.
|
||||
bool isAlwaysPartiallyVisible = mods.OfType<OsuModHidden>().Any(m => m.OnlyFadeApproachCircles.Value) || mods.OfType<OsuModTraceable>().Any();
|
||||
|
||||
// Start from normal curve, rewarding lower AR up to AR7
|
||||
// TC forcefully requires a lower reading bonus for now as it's post-applied in PP which makes it multiplicative with the regular AR bonuses
|
||||
// This means it has an advantage over HD, so we decrease the multiplier to compensate
|
||||
// This should be removed once we're able to apply TC bonuses in SR (depends on real-time difficulty calculations being possible)
|
||||
double readingBonus = (isAlwaysPartiallyVisible ? 0.025 : 0.04) * (12.0 - Math.Max(approachRate, 7));
|
||||
|
||||
readingBonus *= visibilityFactor;
|
||||
|
||||
// We want to reward slideraim on low AR less
|
||||
double sliderVisibilityFactor = Math.Pow(sliderFactor, 3);
|
||||
|
||||
// For AR up to 0 - reduce reward for very low ARs when object is visible
|
||||
if (approachRate < 7)
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.02 : 0.045) * (7.0 - Math.Max(approachRate, 0)) * sliderVisibilityFactor;
|
||||
|
||||
// Starting from AR0 - cap values so they won't grow to infinity
|
||||
if (approachRate < 0)
|
||||
readingBonus += (isAlwaysPartiallyVisible ? 0.01 : 0.1) * (1 - Math.Pow(1.5, approachRate)) * sliderVisibilityFactor;
|
||||
|
||||
return readingBonus;
|
||||
}
|
||||
|
||||
public static double CalculateDifficultyRating(double difficultyValue) => Math.Sqrt(difficultyValue) * difficulty_multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@@ -35,6 +36,22 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
/// </summary>
|
||||
public readonly double AdjustedDeltaTime;
|
||||
|
||||
/// <summary>
|
||||
/// Amount of time elapsed between lastDifficultyObject's <see cref="DifficultyHitObject.EndTime"/> and <see cref="DifficultyHitObject.StartTime"/> capped to a minimum of <see cref="MIN_DELTA_TIME"/>ms.
|
||||
/// </summary>
|
||||
public double LastObjectEndDeltaTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time (in ms) between the object first appearing and the time it needs to be clicked.
|
||||
/// <see cref="OsuHitObject.TimePreempt"/> adjusted by clock rate.
|
||||
/// </summary>
|
||||
public readonly double Preempt;
|
||||
|
||||
/// <summary>
|
||||
/// Normalised distance from the start position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// </summary>
|
||||
public double JumpDistance { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Normalised distance from the "lazy" end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||
/// <para>
|
||||
@@ -101,9 +118,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
public double? Angle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the full hit window for a Great <see cref="HitResult"/>.
|
||||
/// Angle of the vector created between current and current-1
|
||||
/// normalised to consider symmetrical vectors in any axis to be the same angle.
|
||||
/// </summary>
|
||||
public double HitWindowGreat { get; private set; }
|
||||
public double? NormalisedVectorAngle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Selective bonus for maps with higher circle size.
|
||||
@@ -121,17 +139,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||
AdjustedDeltaTime = Math.Max(DeltaTime, MIN_DELTA_TIME);
|
||||
LastObjectEndDeltaTime = lastDifficultyObject != null ? Math.Max(StartTime - lastDifficultyObject.EndTime, MIN_DELTA_TIME) : AdjustedDeltaTime;
|
||||
|
||||
SmallCircleBonus = Math.Max(1.0, 1.0 + (30 - BaseObject.Radius) / 40);
|
||||
SmallCircleBonus = Math.Max(1.0, 1.0 + (30 - BaseObject.Radius) / 70);
|
||||
|
||||
if (BaseObject is Slider sliderObject)
|
||||
{
|
||||
HitWindowGreat = 2 * sliderObject.HeadCircle.HitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||
}
|
||||
else
|
||||
{
|
||||
HitWindowGreat = 2 * BaseObject.HitWindows.WindowFor(HitResult.Great) / clockRate;
|
||||
}
|
||||
Preempt = BaseObject.TimePreempt / clockRate;
|
||||
|
||||
computeSliderCursorPosition();
|
||||
setDistances(clockRate);
|
||||
@@ -148,7 +160,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
|
||||
double fadeInStartTime = BaseObject.StartTime - BaseObject.TimePreempt;
|
||||
double fadeInDuration = BaseObject.TimeFadeIn;
|
||||
|
||||
// Equal to `OsuHitObject.TimeFadeIn` minus any adjustments from the HD mod.
|
||||
double fadeInDuration = 400 * Math.Min(1, BaseObject.TimePreempt / OsuHitObject.PREEMPT_MIN);
|
||||
|
||||
if (hidden)
|
||||
{
|
||||
@@ -175,10 +189,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
{
|
||||
double currDeltaTime = Math.Max(1, DeltaTime);
|
||||
double nextDeltaTime = Math.Max(1, osuNextObj.DeltaTime);
|
||||
|
||||
double deltaDifference = Math.Abs(nextDeltaTime - currDeltaTime);
|
||||
|
||||
double speedRatio = currDeltaTime / Math.Max(currDeltaTime, deltaDifference);
|
||||
double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / HitWindowGreat), 2);
|
||||
return 1.0 - Math.Pow(speedRatio, 1 - windowRatio);
|
||||
double windowRatio = Math.Pow(Math.Min(1, currDeltaTime / HitWindow(HitResult.Great)), 5);
|
||||
|
||||
// Can't doubletap if circles don't intersect
|
||||
double distanceFactor = Math.Pow(DifficultyCalculationUtils.ReverseLerp(LazyJumpDistance, NORMALISED_DIAMETER, NORMALISED_RADIUS), 2);
|
||||
|
||||
return 1.0 - Math.Pow(speedRatio, distanceFactor * (1 - windowRatio));
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -189,10 +209,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
if (BaseObject is Slider currentSlider)
|
||||
{
|
||||
// Bonus for repeat sliders until a better per nested object strain system can be achieved.
|
||||
TravelDistance = LazyTravelDistance * Math.Pow(1 + currentSlider.RepeatCount / 2.5, 1.0 / 2.5);
|
||||
TravelDistance = LazyTravelDistance * Math.Max(1, Math.Pow(currentSlider.RepeatCount, 0.3));
|
||||
TravelTime = Math.Max(LazyTravelTime / clockRate, MIN_DELTA_TIME);
|
||||
}
|
||||
|
||||
MinimumJumpTime = AdjustedDeltaTime;
|
||||
|
||||
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
||||
if (BaseObject is Spinner || LastObject is Spinner)
|
||||
return;
|
||||
@@ -202,8 +224,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
Vector2 lastCursorPosition = lastDifficultyObject != null ? getEndCursorPosition(lastDifficultyObject) : LastObject.StackedPosition;
|
||||
|
||||
LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||
MinimumJumpTime = AdjustedDeltaTime;
|
||||
JumpDistance = (LastObject.StackedPosition - BaseObject.StackedPosition).Length * scalingFactor;
|
||||
LazyJumpDistance = (BaseObject.StackedPosition - lastCursorPosition).Length * scalingFactor;
|
||||
MinimumJumpDistance = LazyJumpDistance;
|
||||
|
||||
if (LastObject is Slider lastSlider && lastDifficultyObject != null)
|
||||
@@ -239,15 +261,18 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
if (lastLastDifficultyObject != null && lastLastDifficultyObject.BaseObject is not Spinner)
|
||||
{
|
||||
if (lastDifficultyObject!.BaseObject is Slider prevSlider && lastDifficultyObject.TravelDistance > 0)
|
||||
lastCursorPosition = prevSlider.HeadCircle.StackedPosition;
|
||||
|
||||
Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastDifficultyObject);
|
||||
|
||||
Vector2 v1 = lastLastCursorPosition - LastObject.StackedPosition;
|
||||
Vector2 v2 = BaseObject.StackedPosition - lastCursorPosition;
|
||||
double angle = calculateAngle(BaseObject.StackedPosition, lastCursorPosition, lastLastCursorPosition);
|
||||
double sliderAngle = calculateSliderAngle(lastDifficultyObject!, lastLastCursorPosition);
|
||||
|
||||
float dot = Vector2.Dot(v1, v2);
|
||||
float det = v1.X * v2.Y - v1.Y * v2.X;
|
||||
Vector2 v = BaseObject.StackedPosition - lastCursorPosition;
|
||||
NormalisedVectorAngle = Math.Atan2(Math.Abs(v.Y), Math.Abs(v.X));
|
||||
|
||||
Angle = Math.Abs(Math.Atan2(det, dot));
|
||||
Angle = Math.Min(angle, sliderAngle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,6 +384,30 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
}
|
||||
}
|
||||
|
||||
private double calculateSliderAngle(OsuDifficultyHitObject lastDifficultyObject, Vector2 lastLastCursorPosition)
|
||||
{
|
||||
Vector2 lastCursorPosition = getEndCursorPosition(lastDifficultyObject);
|
||||
|
||||
if (lastDifficultyObject.BaseObject is Slider prevSlider && lastDifficultyObject.TravelDistance > 0)
|
||||
{
|
||||
OsuHitObject secondLastNestedObject = (OsuHitObject)prevSlider.NestedHitObjects[^2];
|
||||
lastLastCursorPosition = secondLastNestedObject.StackedPosition;
|
||||
}
|
||||
|
||||
return calculateAngle(BaseObject.StackedPosition, lastCursorPosition, lastLastCursorPosition);
|
||||
}
|
||||
|
||||
private double calculateAngle(Vector2 currentPosition, Vector2 lastPosition, Vector2 lastLastPosition)
|
||||
{
|
||||
Vector2 v1 = lastLastPosition - lastPosition;
|
||||
Vector2 v2 = currentPosition - lastPosition;
|
||||
|
||||
float dot = Vector2.Dot(v1, v2);
|
||||
float det = v1.X * v2.Y - v1.Y * v2.X;
|
||||
|
||||
return Math.Abs(Math.Atan2(det, dot));
|
||||
}
|
||||
|
||||
private Vector2 getEndCursorPosition(OsuDifficultyHitObject difficultyHitObject)
|
||||
{
|
||||
return difficultyHitObject.LazyEndPosition ?? difficultyHitObject.BaseObject.StackedPosition;
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators.Aim;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
@@ -15,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// <summary>
|
||||
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
|
||||
/// </summary>
|
||||
public class Aim : OsuStrainSkill
|
||||
public class Aim : VariableLengthStrainSkill
|
||||
{
|
||||
public readonly bool IncludeSliders;
|
||||
|
||||
@@ -27,19 +31,39 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
private double currentStrain;
|
||||
|
||||
private double skillMultiplier => 26;
|
||||
private double strainDecayBase => 0.15;
|
||||
private double skillMultiplierSnap => 70.9;
|
||||
private double skillMultiplierAgility => 2.35;
|
||||
private double skillMultiplierFlow => 242.0;
|
||||
private double skillMultiplierTotal => 1.12;
|
||||
private double combinedSnapNormExponent => 1.2;
|
||||
|
||||
/// <summary>
|
||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||
/// This is done in order to decrease their impact on the overall difficulty of the map for this skill.
|
||||
/// </summary>
|
||||
private int reducedSectionTime => 4000;
|
||||
|
||||
/// <summary>
|
||||
/// The baseline multiplier applied to the section with the biggest strain.
|
||||
/// </summary>
|
||||
private double reducedStrainBaseline => 0.727;
|
||||
|
||||
private readonly List<double> sliderStrains = new List<double>();
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
private double strainDecay(double ms) => Math.Pow(0.2, ms / 1000);
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) =>
|
||||
currentStrain * strainDecay(time - current.Previous(0).StartTime);
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * skillMultiplier;
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
return 0;
|
||||
|
||||
double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
|
||||
currentStrain *= decay;
|
||||
currentStrain += calculateModAdjustedDifficulty(current) * (1 - decay);
|
||||
|
||||
if (current.BaseObject is Slider)
|
||||
sliderStrains.Add(currentStrain);
|
||||
@@ -47,6 +71,73 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
return currentStrain;
|
||||
}
|
||||
|
||||
private double calculateModAdjustedDifficulty(DifficultyHitObject current)
|
||||
{
|
||||
double snapDifficulty = SnapAimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * skillMultiplierSnap;
|
||||
double agilityDifficulty = AgilityEvaluator.EvaluateDifficultyOf(current) * skillMultiplierAgility;
|
||||
double flowDifficulty = FlowAimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * skillMultiplierFlow;
|
||||
|
||||
double totalDifficulty = calculateTotalValue(snapDifficulty, agilityDifficulty, flowDifficulty);
|
||||
|
||||
if (Mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
float magnetisedStrength = Mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
totalDifficulty *= 1.0 - magnetisedStrength;
|
||||
}
|
||||
|
||||
return totalDifficulty;
|
||||
}
|
||||
|
||||
private double calculateTotalValue(double snapDifficulty, double agilityDifficulty, double flowDifficulty)
|
||||
{
|
||||
// We compare flow to combined snap and agility because snap by itself doesn't have enough difficulty to be above flow on streams
|
||||
// Agility on the other hand is supposed to measure the rate of cursor velocity changes while snapping
|
||||
// So snapping every circle on a stream requires an enormous amount of agility at which point it's easier to flow
|
||||
double combinedSnapDifficulty = DifficultyCalculationUtils.Norm(combinedSnapNormExponent, snapDifficulty, agilityDifficulty);
|
||||
|
||||
double pSnap = calculateSnapFlowProbability(flowDifficulty / combinedSnapDifficulty);
|
||||
double pFlow = 1 - pSnap;
|
||||
|
||||
if (Mods.Any(m => m is OsuModTouchDevice))
|
||||
{
|
||||
// we don't adjust agility here since agility represents TD difficulty in a decent enough way
|
||||
snapDifficulty = Math.Pow(snapDifficulty, 0.89);
|
||||
combinedSnapDifficulty = DifficultyCalculationUtils.Norm(combinedSnapNormExponent, snapDifficulty, agilityDifficulty);
|
||||
}
|
||||
|
||||
if (Mods.Any(m => m is OsuModRelax))
|
||||
{
|
||||
combinedSnapDifficulty *= 0.75;
|
||||
flowDifficulty *= 0.6;
|
||||
}
|
||||
|
||||
double totalDifficulty = combinedSnapDifficulty * pSnap + flowDifficulty * pFlow;
|
||||
|
||||
double totalStrain = totalDifficulty * skillMultiplierTotal;
|
||||
|
||||
return totalStrain;
|
||||
}
|
||||
|
||||
// A function that turns the ratio of snap : flow into the probability of snapping/flowing
|
||||
// It has the constraints:
|
||||
// P(snap) + P(flow) = 1 (the object is always either snapped or flowed)
|
||||
// P(snap) = f(snap/flow), P(flow) = f(flow/snap) (ie snap and flow are symmetric and reversible)
|
||||
// Therefore: f(x) + f(1/x) = 1
|
||||
// 0 <= f(x) <= 1 (cannot have negative or greater than 100% probability of snapping or flowing)
|
||||
// This logistic function is a solution, which fits nicely with the general idea of interpolation and provides a tuneable constant
|
||||
private static double calculateSnapFlowProbability(double ratio)
|
||||
{
|
||||
const double k = 7.27;
|
||||
|
||||
if (ratio == 0)
|
||||
return 0;
|
||||
|
||||
if (double.IsNaN(ratio))
|
||||
return 1;
|
||||
|
||||
return DifficultyCalculationUtils.Logistic(-k * Math.Log(ratio));
|
||||
}
|
||||
|
||||
public double GetDifficultSliders()
|
||||
{
|
||||
if (sliderStrains.Count == 0)
|
||||
@@ -60,6 +151,97 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
return sliderStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxSliderStrain * 12.0 - 6.0))));
|
||||
}
|
||||
|
||||
public double CountTopWeightedSliders() => OsuStrainUtils.CountTopWeightedSliders(sliderStrains, DifficultyValue());
|
||||
public double CountTopWeightedSliders(double difficultyValue)
|
||||
{
|
||||
if (sliderStrains.Count == 0)
|
||||
return 0;
|
||||
|
||||
double consistentTopStrain = difficultyValue * (1 - DecayWeight); // What would the top strain be if all strain values were identical
|
||||
|
||||
if (consistentTopStrain == 0)
|
||||
return 0;
|
||||
|
||||
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
|
||||
return sliderStrains.Sum(s => DifficultyCalculationUtils.Logistic(s / consistentTopStrain, 0.88, 10, 1.1));
|
||||
}
|
||||
|
||||
public override double DifficultyValue()
|
||||
{
|
||||
double difficulty = 0;
|
||||
double time = 0;
|
||||
|
||||
var strains = getReducedStrainPeaks();
|
||||
|
||||
// Difficulty is a continuous weighted sum of the sorted strains
|
||||
foreach (StrainPeak strain in strains)
|
||||
{
|
||||
/* Weighting function can be thought of as:
|
||||
b
|
||||
∫ DecayWeight^x dx
|
||||
a
|
||||
where a = startTime and b = endTime
|
||||
|
||||
Technically, the function below has been slightly modified from the equation above.
|
||||
The real function would be
|
||||
double weight = Math.Pow(DecayWeight, startTime) - Math.Pow(DecayWeight, endTime);
|
||||
...
|
||||
return difficulty / Math.Log(1 / DecayWeight);
|
||||
E.g. for a DecayWeight of 0.9, we're multiplying by 10 instead of 9.49122...
|
||||
|
||||
This change makes it so that a map composed solely of MaxSectionLength chunks will have the exact same value when summed in this class and StrainSkill.
|
||||
Doing this ensures the relationship between strain values and difficulty values remains the same between the two classes.
|
||||
*/
|
||||
double startTime = time;
|
||||
double endTime = time + strain.SectionLength / MaxSectionLength;
|
||||
|
||||
double weight = Math.Pow(DecayWeight, startTime) - Math.Pow(DecayWeight, endTime);
|
||||
|
||||
difficulty += strain.Value * weight;
|
||||
time = endTime;
|
||||
}
|
||||
|
||||
return difficulty / (1 - DecayWeight);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sorted enumerable of strain peaks with the highest values reduced.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<StrainPeak> getReducedStrainPeaks()
|
||||
{
|
||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// These sections will not contribute to the difficulty.
|
||||
var peaks = GetCurrentStrainPeaks().Where(p => p.Value > 0);
|
||||
|
||||
List<StrainPeak> strains = peaks.OrderByDescending(p => p.Value).ToList();
|
||||
|
||||
const int chunk_size = 20;
|
||||
double time = 0;
|
||||
int strainsToRemove = 0; // All strains are removed at the end for optimization purposes
|
||||
|
||||
// We are reducing the highest strains first to account for extreme difficulty spikes
|
||||
// Strains are split into 20ms chunks to try to mitigate inconsistencies caused by reducing strains
|
||||
while (strains.Count > strainsToRemove && time < reducedSectionTime)
|
||||
{
|
||||
StrainPeak strain = strains[strainsToRemove];
|
||||
|
||||
for (double addedTime = 0; addedTime < strain.SectionLength; addedTime += chunk_size)
|
||||
{
|
||||
double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((time + addedTime) / reducedSectionTime, 0, 1)));
|
||||
|
||||
strains.Add(new StrainPeak(
|
||||
strain.Value * Interpolation.Lerp(reducedStrainBaseline, 1.0, scale),
|
||||
Math.Min(chunk_size, strain.SectionLength - addedTime)
|
||||
));
|
||||
}
|
||||
|
||||
time += strain.SectionLength;
|
||||
strainsToRemove++;
|
||||
}
|
||||
|
||||
strains.RemoveRange(0, strainsToRemove);
|
||||
|
||||
return strains.OrderByDescending(p => p.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
@@ -16,15 +17,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
/// </summary>
|
||||
public class Flashlight : StrainSkill
|
||||
{
|
||||
private readonly bool hasHiddenMod;
|
||||
|
||||
public Flashlight(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
hasHiddenMod = mods.Any(m => m is OsuModHidden);
|
||||
}
|
||||
|
||||
private double skillMultiplier => 0.05512;
|
||||
private double skillMultiplier => 0.058;
|
||||
private double strainDecayBase => 0.15;
|
||||
|
||||
private double currentStrain;
|
||||
@@ -35,12 +33,43 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
{
|
||||
if (!Mods.Any(m => m is OsuModFlashlight))
|
||||
return 0;
|
||||
|
||||
currentStrain *= strainDecay(current.DeltaTime);
|
||||
currentStrain += FlashlightEvaluator.EvaluateDifficultyOf(current, hasHiddenMod) * skillMultiplier;
|
||||
currentStrain += calculateModAdjustedDifficulty(current) * skillMultiplier;
|
||||
|
||||
return currentStrain;
|
||||
}
|
||||
|
||||
private double calculateModAdjustedDifficulty(DifficultyHitObject current)
|
||||
{
|
||||
double difficulty = FlashlightEvaluator.EvaluateDifficultyOf(current, Mods);
|
||||
|
||||
if (Mods.Any(m => m is OsuModTouchDevice))
|
||||
difficulty = Math.Pow(difficulty, 0.9);
|
||||
|
||||
if (Mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
float magnetisedStrength = Mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
difficulty *= 1.0 - magnetisedStrength;
|
||||
}
|
||||
|
||||
if (Mods.Any(m => m is OsuModDeflate))
|
||||
{
|
||||
float deflateInitialScale = Mods.OfType<OsuModDeflate>().First().StartScale.Value;
|
||||
difficulty *= Math.Clamp(DifficultyCalculationUtils.ReverseLerp(deflateInitialScale, 11, 1), 0.1, 1);
|
||||
}
|
||||
|
||||
if (Mods.Any(m => m is OsuModRelax))
|
||||
difficulty *= 0.7;
|
||||
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
difficulty *= 0.4;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public override double DifficultyValue() => GetCurrentStrainPeaks().Sum();
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2);
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
public abstract class OsuStrainSkill : StrainSkill
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||
/// This is done in order to decrease their impact on the overall difficulty of the map for this skill.
|
||||
/// </summary>
|
||||
protected virtual int ReducedSectionCount => 10;
|
||||
|
||||
/// <summary>
|
||||
/// The baseline multiplier applied to the section with the biggest strain.
|
||||
/// </summary>
|
||||
protected virtual double ReducedStrainBaseline => 0.75;
|
||||
|
||||
protected OsuStrainSkill(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override double DifficultyValue()
|
||||
{
|
||||
double difficulty = 0;
|
||||
double weight = 1;
|
||||
|
||||
// Sections with 0 strain are excluded to avoid worst-case time complexity of the following sort (e.g. /b/2351871).
|
||||
// These sections will not contribute to the difficulty.
|
||||
var peaks = GetCurrentStrainPeaks().Where(p => p > 0);
|
||||
|
||||
List<double> strains = peaks.OrderDescending().ToList();
|
||||
|
||||
// We are reducing the highest strains first to account for extreme difficulty spikes
|
||||
for (int i = 0; i < Math.Min(strains.Count, ReducedSectionCount); i++)
|
||||
{
|
||||
double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((float)i / ReducedSectionCount, 0, 1)));
|
||||
strains[i] *= Interpolation.Lerp(ReducedStrainBaseline, 1.0, scale);
|
||||
}
|
||||
|
||||
// Difficulty is the weighted sum of the highest strains from every section.
|
||||
// We're sorting from highest to lowest strain.
|
||||
foreach (double strain in strains.OrderDescending())
|
||||
{
|
||||
difficulty += strain * weight;
|
||||
weight *= DecayWeight;
|
||||
}
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
public class Reading : HarmonicSkill
|
||||
{
|
||||
private readonly List<DifficultyHitObject> objectList = new List<DifficultyHitObject>();
|
||||
|
||||
private readonly bool hasHiddenMod;
|
||||
|
||||
public Reading(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
hasHiddenMod = mods.OfType<OsuModHidden>().Any(m => !m.OnlyFadeApproachCircles.Value);
|
||||
}
|
||||
|
||||
private double currentStrain;
|
||||
|
||||
private double skillMultiplier => 2.5;
|
||||
private double strainDecayBase => 0.8;
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
||||
protected override double ObjectDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
objectList.Add(current);
|
||||
|
||||
double decay = strainDecay(current.DeltaTime);
|
||||
|
||||
currentStrain *= decay;
|
||||
currentStrain += calculateModAdjustedDifficulty(current) * (1 - decay) * skillMultiplier;
|
||||
|
||||
return currentStrain;
|
||||
}
|
||||
|
||||
private double calculateModAdjustedDifficulty(DifficultyHitObject current)
|
||||
{
|
||||
double difficulty = ReadingEvaluator.EvaluateDifficultyOf(current, hasHiddenMod);
|
||||
|
||||
if (Mods.Any(m => m is OsuModTouchDevice))
|
||||
difficulty = Math.Pow(difficulty, 0.89);
|
||||
|
||||
if (Mods.Any(m => m is OsuModMagnetised))
|
||||
{
|
||||
float magnetisedStrength = Mods.OfType<OsuModMagnetised>().First().AttractionStrength.Value;
|
||||
difficulty *= 1.0 - magnetisedStrength;
|
||||
}
|
||||
|
||||
if (Mods.Any(m => m is OsuModRelax))
|
||||
difficulty *= 0.4;
|
||||
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
difficulty *= 0.1;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
protected override void ApplyDifficultyTransformation(double[] difficulties)
|
||||
{
|
||||
const double reduced_difficulty_base_line = 0.0; // Assume the first seconds are completely memorised
|
||||
|
||||
int reducedNoteCount = calculateReducedNoteCount();
|
||||
|
||||
for (int i = 0; i < Math.Min(difficulties.Length, reducedNoteCount); i++)
|
||||
{
|
||||
double scale = Math.Log10(Interpolation.Lerp(1, 10, Math.Clamp((double)i / reducedNoteCount, 0, 1)));
|
||||
difficulties[i] *= Interpolation.Lerp(reduced_difficulty_base_line, 1.0, scale);
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateReducedNoteCount()
|
||||
{
|
||||
const double reduced_difficulty_duration = 60 * 1000;
|
||||
|
||||
if (objectList.Count == 0)
|
||||
return 0;
|
||||
|
||||
double reducedDuration = objectList.First().StartTime + reduced_difficulty_duration;
|
||||
|
||||
int reducedNoteCount = 0;
|
||||
|
||||
foreach (var hitObject in objectList)
|
||||
{
|
||||
if (hitObject.StartTime > reducedDuration)
|
||||
break;
|
||||
|
||||
reducedNoteCount++;
|
||||
}
|
||||
|
||||
return reducedNoteCount;
|
||||
}
|
||||
|
||||
public override double CountTopWeightedObjectDifficulties(double difficultyValue)
|
||||
{
|
||||
if (ObjectDifficulties.Count == 0)
|
||||
return 0.0;
|
||||
|
||||
if (NoteWeightSum == 0)
|
||||
return 0.0;
|
||||
|
||||
double consistentTopNote = difficultyValue / NoteWeightSum; // What would the top difficulty be if all object difficulties were identical
|
||||
|
||||
if (consistentTopNote == 0)
|
||||
return 0;
|
||||
|
||||
return ObjectDifficulties.Sum(d => DifficultyCalculationUtils.Logistic(d / consistentTopNote, 1.15, 5, 1.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,30 +3,33 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Evaluators.Speed;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
|
||||
/// </summary>
|
||||
public class Speed : OsuStrainSkill
|
||||
public class Speed : HarmonicSkill
|
||||
{
|
||||
private double skillMultiplier => 1.47;
|
||||
private double strainDecayBase => 0.3;
|
||||
|
||||
private double currentStrain;
|
||||
private double currentRhythm;
|
||||
private double skillMultiplier => 1.16;
|
||||
|
||||
private readonly List<double> sliderStrains = new List<double>();
|
||||
|
||||
protected override int ReducedSectionCount => 5;
|
||||
private double currentStrain;
|
||||
|
||||
private double strainDecayBase => 0.3;
|
||||
|
||||
protected override double HarmonicScale => 20;
|
||||
protected override double DecayExponent => 0.9;
|
||||
|
||||
public Speed(Mod[] mods)
|
||||
: base(mods)
|
||||
@@ -35,14 +38,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||
|
||||
protected override double CalculateInitialStrain(double time, DifficultyHitObject current) => (currentStrain * currentRhythm) * strainDecay(time - current.Previous(0).StartTime);
|
||||
|
||||
protected override double StrainValueAt(DifficultyHitObject current)
|
||||
protected override double ObjectDifficultyOf(DifficultyHitObject current)
|
||||
{
|
||||
currentStrain *= strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
currentStrain += SpeedEvaluator.EvaluateDifficultyOf(current, Mods) * skillMultiplier;
|
||||
if (Mods.Any(m => m is OsuModRelax))
|
||||
return 0;
|
||||
|
||||
currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
double decay = strainDecay(((OsuDifficultyHitObject)current).AdjustedDeltaTime);
|
||||
|
||||
currentStrain *= decay;
|
||||
currentStrain += calculateModAdjustedDifficulty(current) * (1 - decay) * skillMultiplier;
|
||||
|
||||
double currentRhythm = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
double totalStrain = currentStrain * currentRhythm;
|
||||
|
||||
@@ -52,18 +58,44 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
return totalStrain;
|
||||
}
|
||||
|
||||
private double calculateModAdjustedDifficulty(DifficultyHitObject current)
|
||||
{
|
||||
double difficulty = SpeedEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
if (Mods.Any(m => m is OsuModAutopilot))
|
||||
difficulty *= 0.5;
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
public double RelevantNoteCount()
|
||||
{
|
||||
if (ObjectStrains.Count == 0)
|
||||
if (ObjectDifficulties.Count == 0)
|
||||
return 0;
|
||||
|
||||
double maxStrain = ObjectStrains.Max();
|
||||
double maxStrain = ObjectDifficulties.Max();
|
||||
|
||||
if (maxStrain == 0)
|
||||
return 0;
|
||||
|
||||
return ObjectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0))));
|
||||
return ObjectDifficulties.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0))));
|
||||
}
|
||||
|
||||
public double CountTopWeightedSliders() => OsuStrainUtils.CountTopWeightedSliders(sliderStrains, DifficultyValue());
|
||||
public double CountTopWeightedSliders(double difficultyValue)
|
||||
{
|
||||
if (sliderStrains.Count == 0)
|
||||
return 0;
|
||||
|
||||
if (NoteWeightSum == 0)
|
||||
return 0.0;
|
||||
|
||||
double consistentTopNote = difficultyValue / NoteWeightSum; // What would the top note be if all note values were identical
|
||||
|
||||
if (consistentTopNote == 0)
|
||||
return 0;
|
||||
|
||||
// Use a weighted sum of all notes. Constants are arbitrary and give nice values
|
||||
return sliderStrains.Sum(s => DifficultyCalculationUtils.Logistic(s / consistentTopNote, 0.88, 10, 1.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty.Utils
|
||||
{
|
||||
public static class OsuStrainUtils
|
||||
{
|
||||
public static double CountTopWeightedSliders(IReadOnlyCollection<double> sliderStrains, double difficultyValue)
|
||||
{
|
||||
if (sliderStrains.Count == 0)
|
||||
return 0;
|
||||
|
||||
double consistentTopStrain = difficultyValue / 10; // What would the top strain be if all strain values were identical
|
||||
|
||||
if (consistentTopStrain == 0)
|
||||
return 0;
|
||||
|
||||
// Use a weighted sum of all strains. Constants are arbitrary and give nice values
|
||||
return sliderStrains.Sum(s => DifficultyCalculationUtils.Logistic(s / consistentTopStrain, 0.88, 10, 1.1));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Utils;
|
||||
@@ -37,13 +38,16 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[Resolved]
|
||||
private IEditorChangeHandler? changeHandler { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider? snapProvider { get; set; }
|
||||
|
||||
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(EditorBeatmap editorBeatmap)
|
||||
private void load()
|
||||
{
|
||||
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
|
||||
}
|
||||
@@ -53,15 +57,22 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
base.LoadComplete();
|
||||
|
||||
selectedItems.CollectionChanged += (_, __) => updateState();
|
||||
editorBeatmap.HitObjectUpdated += hitObjectUpdated;
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void hitObjectUpdated(HitObject hitObject)
|
||||
{
|
||||
if (selectedMovableObjects.Contains(hitObject))
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
|
||||
|
||||
CanScaleX.Value = quad.Width > 0;
|
||||
CanScaleY.Value = quad.Height > 0;
|
||||
CanScaleX.Value = Precision.DefinitelyBigger(quad.Width, 0);
|
||||
CanScaleY.Value = Precision.DefinitelyBigger(quad.Height, 0);
|
||||
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
|
||||
CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
|
||||
IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
|
||||
@@ -339,5 +350,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (editorBeatmap.IsNotNull())
|
||||
editorBeatmap.HitObjectUpdated -= hitObjectUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,14 +62,22 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
if (osuObject is not Spinner)
|
||||
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||
|
||||
int repeatCount = 0;
|
||||
|
||||
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
switch (nested)
|
||||
{
|
||||
//Freezing the SliderTicks doesnt play well with snaking sliders
|
||||
// Freezing the SliderTicks doesnt play well with snaking sliders
|
||||
case SliderTick:
|
||||
//SliderRepeat wont layer correctly if preempt is changed.
|
||||
break;
|
||||
|
||||
case SliderRepeat:
|
||||
if (repeatCount > 2)
|
||||
break;
|
||||
|
||||
applyFadeInAdjustment(nested);
|
||||
repeatCount++;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -277,19 +277,24 @@ namespace osu.Game.Rulesets.Osu
|
||||
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
HitResult.Meh,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.LargeTickHit,
|
||||
HitResult.LargeTickMiss,
|
||||
HitResult.SmallTickHit,
|
||||
HitResult.SmallTickMiss,
|
||||
HitResult.SliderTailHit,
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
|
||||
string export = LocalStorage.GetFiles("exports").First();
|
||||
|
||||
using (var stream = LocalStorage.GetStream(export))
|
||||
using (var zip = ZipArchive.Open(stream))
|
||||
using (var zip = ZipArchive.OpenArchive(stream))
|
||||
{
|
||||
using (var osuStream = zip.Entries.First().OpenEntryStream())
|
||||
using (var reader = new StreamReader(osuStream))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\osu.TestProject.props" />
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageReference Include="NUnit" Version="4.5.1" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="6.1.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Project">
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -5,9 +5,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
{
|
||||
@@ -16,8 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
/// <summary>
|
||||
/// Evaluate the difficulty of a hitobject considering its interval change.
|
||||
/// </summary>
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject, double hitWindow)
|
||||
public static double EvaluateDifficultyOf(DifficultyHitObject hitObject)
|
||||
{
|
||||
if (hitObject.BaseObject is not Hit)
|
||||
return 0;
|
||||
|
||||
TaikoRhythmData rhythmData = ((TaikoDifficultyHitObject)hitObject).RhythmData;
|
||||
double difficulty = 0.0d;
|
||||
|
||||
@@ -25,6 +30,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
double samePattern = 0;
|
||||
double intervalPenalty = 0;
|
||||
|
||||
double hitWindow = hitObject.HitWindow(HitResult.Great);
|
||||
|
||||
if (rhythmData.SameRhythmGroupedHitObjects?.FirstHitObject == hitObject) // Difficulty for SameRhythmGroupedHitObjects
|
||||
{
|
||||
sameRhythm += 10.0 * evaluateDifficultyOf(rhythmData.SameRhythmGroupedHitObjects, hitWindow);
|
||||
@@ -56,8 +63,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
{
|
||||
intervalDifficulty *= DifficultyCalculationUtils.Logistic(
|
||||
durationDifference / hitWindow,
|
||||
midpointOffset: 0.7,
|
||||
multiplier: 1.0,
|
||||
midpointOffset: 0.35,
|
||||
multiplier: 2,
|
||||
maxValue: 1);
|
||||
}
|
||||
}
|
||||
@@ -65,8 +72,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Evaluators
|
||||
// Penalise patterns that can be hit within a single hit window.
|
||||
intervalDifficulty *= DifficultyCalculationUtils.Logistic(
|
||||
sameRhythmGroupedHitObjects.Duration / hitWindow,
|
||||
midpointOffset: 0.6,
|
||||
multiplier: 1,
|
||||
midpointOffset: 0.3,
|
||||
multiplier: 2,
|
||||
maxValue: 1);
|
||||
|
||||
return Math.Pow(intervalDifficulty, 0.75);
|
||||
|
||||
@@ -7,10 +7,10 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Evaluators;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
|
||||
{
|
||||
|
||||
@@ -17,17 +17,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||
protected override double SkillMultiplier => 1.0;
|
||||
protected override double StrainDecayBase => 0.4;
|
||||
|
||||
private readonly double greatHitWindow;
|
||||
|
||||
public Rhythm(Mod[] mods, double greatHitWindow)
|
||||
public Rhythm(Mod[] mods)
|
||||
: base(mods)
|
||||
{
|
||||
this.greatHitWindow = greatHitWindow;
|
||||
}
|
||||
|
||||
protected override double StrainValueOf(DifficultyHitObject current)
|
||||
{
|
||||
double difficulty = RhythmEvaluator.EvaluateDifficultyOf(current, greatHitWindow);
|
||||
double difficulty = RhythmEvaluator.EvaluateDifficultyOf(current);
|
||||
|
||||
// To prevent abuse of exceedingly long intervals between awkward rhythms, we penalise its difficulty.
|
||||
double staminaDifficulty = StaminaEvaluator.EvaluateDifficultyOf(current) - 0.5; // Remove base strain
|
||||
|
||||
@@ -10,13 +10,12 @@ using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Difficulty.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
@@ -41,17 +40,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
}
|
||||
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
|
||||
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
HitWindows hitWindows = new TaikoHitWindows();
|
||||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||
|
||||
isConvert = beatmap.BeatmapInfo.Ruleset.OnlineID == 0;
|
||||
isRelax = mods.Any(h => h is TaikoModRelax);
|
||||
|
||||
return new Skill[]
|
||||
{
|
||||
new Rhythm(mods, hitWindows.WindowFor(HitResult.Great) / clockRate),
|
||||
new Rhythm(mods),
|
||||
new Reading(mods),
|
||||
new Colour(mods),
|
||||
new Stamina(mods, false, isConvert),
|
||||
@@ -67,13 +63,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
new TaikoModHardRock(),
|
||||
};
|
||||
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, Mod[] mods)
|
||||
{
|
||||
var difficultyHitObjects = new List<DifficultyHitObject>();
|
||||
var centreObjects = new List<TaikoDifficultyHitObject>();
|
||||
var rimObjects = new List<TaikoDifficultyHitObject>();
|
||||
var noteObjects = new List<TaikoDifficultyHitObject>();
|
||||
|
||||
double clockRate = ModUtils.CalculateRateWithMods(mods);
|
||||
|
||||
// Generate TaikoDifficultyHitObjects from the beatmap's hit objects.
|
||||
for (int i = 2; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
@@ -97,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
return difficultyHitObjects;
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
|
||||
protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills)
|
||||
{
|
||||
if (beatmap.HitObjects.Count == 0)
|
||||
return new TaikoDifficultyAttributes { Mods = mods };
|
||||
@@ -108,14 +106,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
var stamina = skills.OfType<Stamina>().Single(s => !s.SingleColourStamina);
|
||||
var singleColourStamina = skills.OfType<Stamina>().Single(s => s.SingleColourStamina);
|
||||
|
||||
double staminaDifficultyValue = stamina.DifficultyValue();
|
||||
|
||||
double rhythmSkill = rhythm.DifficultyValue() * rhythm_skill_multiplier;
|
||||
double readingSkill = reading.DifficultyValue() * reading_skill_multiplier;
|
||||
double colourSkill = colour.DifficultyValue() * colour_skill_multiplier;
|
||||
double staminaSkill = stamina.DifficultyValue() * stamina_skill_multiplier;
|
||||
double staminaSkill = staminaDifficultyValue * stamina_skill_multiplier;
|
||||
double monoStaminaSkill = singleColourStamina.DifficultyValue() * stamina_skill_multiplier;
|
||||
double monoStaminaFactor = staminaSkill == 0 ? 1 : Math.Pow(monoStaminaSkill / staminaSkill, 5);
|
||||
|
||||
double staminaDifficultStrains = stamina.CountTopWeightedStrains();
|
||||
double staminaDifficultStrains = stamina.CountTopWeightedStrains(staminaDifficultyValue);
|
||||
|
||||
// As we don't have pattern integration in osu!taiko, we apply the other two skills relative to rhythm.
|
||||
patternMultiplier = Math.Pow(staminaSkill * colourSkill, 0.10);
|
||||
@@ -184,10 +184,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
}
|
||||
|
||||
List<double> hitObjectStrainPeaks = combinePeaks(
|
||||
rhythm.GetObjectStrains().ToList(),
|
||||
reading.GetObjectStrains().ToList(),
|
||||
colour.GetObjectStrains().ToList(),
|
||||
stamina.GetObjectStrains().ToList()
|
||||
rhythm.GetObjectDifficulties(),
|
||||
reading.GetObjectDifficulties(),
|
||||
colour.GetObjectDifficulties(),
|
||||
stamina.GetObjectDifficulties()
|
||||
);
|
||||
|
||||
if (hitObjectStrainPeaks.Count == 0)
|
||||
@@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
/// <summary>
|
||||
/// Combines lists of peak strains from multiple skills into a list of single peak strains for each section.
|
||||
/// </summary>
|
||||
private List<double> combinePeaks(List<double> rhythmPeaks, List<double> readingPeaks, List<double> colourPeaks, List<double> staminaPeaks)
|
||||
private List<double> combinePeaks(IReadOnlyList<double> rhythmPeaks, IReadOnlyList<double> readingPeaks, IReadOnlyList<double> colourPeaks, IReadOnlyList<double> staminaPeaks)
|
||||
{
|
||||
var combinedPeaks = new List<double>();
|
||||
|
||||
|
||||
@@ -122,6 +122,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taikoBeatmap.HitObjects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
|
||||
}
|
||||
|
||||
private int getSnapBetweenNotes(ControlPointInfo controlPointInfo, Hit currentNote, Hit nextNote)
|
||||
|
||||
@@ -222,15 +222,18 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
|
||||
|
||||
protected override IEnumerable<HitResult> GetValidHitResults()
|
||||
public override IEnumerable<HitResult> GetValidHitResults()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
HitResult.Great,
|
||||
HitResult.Ok,
|
||||
HitResult.Miss,
|
||||
|
||||
HitResult.SmallBonus,
|
||||
HitResult.LargeBonus,
|
||||
HitResult.IgnoreHit,
|
||||
HitResult.IgnoreMiss,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -42,9 +43,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var decoder = Decoder.GetDecoder<Beatmap>(stream);
|
||||
var working = new TestWorkingBeatmap(decoder.Decode(stream));
|
||||
|
||||
Assert.AreEqual(6, working.Beatmap.BeatmapVersion);
|
||||
ClassicAssert.AreEqual(6, working.Beatmap.BeatmapVersion);
|
||||
Assert.That(working.Beatmap.BeatmapInfo.Ruleset.Name, Is.Not.EqualTo("null placeholder ruleset"));
|
||||
Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
ClassicAssert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,10 +60,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
((LegacyBeatmapDecoder)decoder).ApplyOffsets = applyOffsets;
|
||||
var working = new TestWorkingBeatmap(decoder.Decode(stream));
|
||||
|
||||
Assert.AreEqual(4, working.Beatmap.BeatmapVersion);
|
||||
Assert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
ClassicAssert.AreEqual(4, working.Beatmap.BeatmapVersion);
|
||||
ClassicAssert.AreEqual(4, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>()).BeatmapVersion);
|
||||
|
||||
Assert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime);
|
||||
ClassicAssert.AreEqual(-1, working.BeatmapInfo.Metadata.PreviewTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,17 +79,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmapInfo = beatmap.BeatmapInfo;
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
||||
Assert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
Assert.AreEqual(164471, metadata.PreviewTime);
|
||||
Assert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
Assert.IsFalse(beatmap.LetterboxInBreaks);
|
||||
Assert.IsFalse(beatmap.SpecialStyle);
|
||||
Assert.IsFalse(beatmap.WidescreenStoryboard);
|
||||
Assert.IsFalse(beatmap.SamplesMatchPlaybackRate);
|
||||
Assert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
Assert.AreEqual(0, beatmap.CountdownOffset);
|
||||
ClassicAssert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
||||
ClassicAssert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
ClassicAssert.AreEqual(164471, metadata.PreviewTime);
|
||||
ClassicAssert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
ClassicAssert.True(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
ClassicAssert.False(beatmap.LetterboxInBreaks);
|
||||
ClassicAssert.False(beatmap.SpecialStyle);
|
||||
ClassicAssert.False(beatmap.WidescreenStoryboard);
|
||||
ClassicAssert.False(beatmap.SamplesMatchPlaybackRate);
|
||||
ClassicAssert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
ClassicAssert.AreEqual(0, beatmap.CountdownOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,13 +109,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
||||
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
||||
};
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
ClassicAssert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
for (int i = 0; i < expectedBookmarks.Length; i++)
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor);
|
||||
Assert.AreEqual(4, beatmap.GridSize);
|
||||
Assert.AreEqual(2, beatmap.TimelineZoom);
|
||||
ClassicAssert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
ClassicAssert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
ClassicAssert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor);
|
||||
ClassicAssert.AreEqual(4, beatmap.GridSize);
|
||||
ClassicAssert.AreEqual(2, beatmap.TimelineZoom);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,16 +131,16 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmapInfo = beatmap.BeatmapInfo;
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("Renatus", metadata.Title);
|
||||
Assert.AreEqual("Renatus", metadata.TitleUnicode);
|
||||
Assert.AreEqual("Soleily", metadata.Artist);
|
||||
Assert.AreEqual("Soleily", metadata.ArtistUnicode);
|
||||
Assert.AreEqual("Gamu", metadata.Author.Username);
|
||||
Assert.AreEqual("Insane", beatmapInfo.DifficultyName);
|
||||
Assert.AreEqual(string.Empty, metadata.Source);
|
||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||
Assert.AreEqual(557821, beatmapInfo.OnlineID);
|
||||
Assert.AreEqual(241526, beatmapInfo.BeatmapSet?.OnlineID);
|
||||
ClassicAssert.AreEqual("Renatus", metadata.Title);
|
||||
ClassicAssert.AreEqual("Renatus", metadata.TitleUnicode);
|
||||
ClassicAssert.AreEqual("Soleily", metadata.Artist);
|
||||
ClassicAssert.AreEqual("Soleily", metadata.ArtistUnicode);
|
||||
ClassicAssert.AreEqual("Gamu", metadata.Author.Username);
|
||||
ClassicAssert.AreEqual("Insane", beatmapInfo.DifficultyName);
|
||||
ClassicAssert.AreEqual(string.Empty, metadata.Source);
|
||||
ClassicAssert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
|
||||
ClassicAssert.AreEqual(557821, beatmapInfo.OnlineID);
|
||||
ClassicAssert.AreEqual(241526, beatmapInfo.BeatmapSet?.OnlineID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,12 +154,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var difficulty = decoder.Decode(stream).Difficulty;
|
||||
|
||||
Assert.AreEqual(6.5f, difficulty.DrainRate);
|
||||
Assert.AreEqual(4, difficulty.CircleSize);
|
||||
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
Assert.AreEqual(9, difficulty.ApproachRate);
|
||||
Assert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||
ClassicAssert.AreEqual(6.5f, difficulty.DrainRate);
|
||||
ClassicAssert.AreEqual(4, difficulty.CircleSize);
|
||||
ClassicAssert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
ClassicAssert.AreEqual(9, difficulty.ApproachRate);
|
||||
ClassicAssert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
ClassicAssert.AreEqual(2, difficulty.SliderTickRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,10 +175,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var metadata = beatmap.Metadata;
|
||||
var breakPoint = beatmap.Breaks[0];
|
||||
|
||||
Assert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile);
|
||||
Assert.AreEqual(122474, breakPoint.StartTime);
|
||||
Assert.AreEqual(140135, breakPoint.EndTime);
|
||||
Assert.IsTrue(breakPoint.HasEffect);
|
||||
ClassicAssert.AreEqual("machinetop_background.jpg", metadata.BackgroundFile);
|
||||
ClassicAssert.AreEqual(122474, breakPoint.StartTime);
|
||||
ClassicAssert.AreEqual(140135, breakPoint.EndTime);
|
||||
ClassicAssert.True(breakPoint.HasEffect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
ClassicAssert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +208,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
ClassicAssert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +223,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
ClassicAssert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,67 +238,67 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var controlPoints = (LegacyControlPointInfo)beatmap.ControlPointInfo;
|
||||
|
||||
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||
Assert.AreEqual(34, controlPoints.SamplePoints.Count);
|
||||
Assert.AreEqual(8, controlPoints.EffectPoints.Count);
|
||||
ClassicAssert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||
ClassicAssert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||
ClassicAssert.AreEqual(34, controlPoints.SamplePoints.Count);
|
||||
ClassicAssert.AreEqual(8, controlPoints.EffectPoints.Count);
|
||||
|
||||
var timingPoint = controlPoints.TimingPointAt(0);
|
||||
Assert.AreEqual(956, timingPoint.Time);
|
||||
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
ClassicAssert.AreEqual(956, timingPoint.Time);
|
||||
ClassicAssert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
||||
ClassicAssert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
ClassicAssert.False(timingPoint.OmitFirstBarLine);
|
||||
|
||||
timingPoint = controlPoints.TimingPointAt(48428);
|
||||
Assert.AreEqual(956, timingPoint.Time);
|
||||
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
ClassicAssert.AreEqual(956, timingPoint.Time);
|
||||
ClassicAssert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||
ClassicAssert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
ClassicAssert.False(timingPoint.OmitFirstBarLine);
|
||||
|
||||
timingPoint = controlPoints.TimingPointAt(119637);
|
||||
Assert.AreEqual(119637, timingPoint.Time);
|
||||
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
ClassicAssert.AreEqual(119637, timingPoint.Time);
|
||||
ClassicAssert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
||||
ClassicAssert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
ClassicAssert.False(timingPoint.OmitFirstBarLine);
|
||||
|
||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
ClassicAssert.AreEqual(0, difficultyPoint.Time);
|
||||
ClassicAssert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
|
||||
difficultyPoint = controlPoints.DifficultyPointAt(48428);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
Assert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
ClassicAssert.AreEqual(0, difficultyPoint.Time);
|
||||
ClassicAssert.AreEqual(1.0, difficultyPoint.SliderVelocity);
|
||||
|
||||
difficultyPoint = controlPoints.DifficultyPointAt(116999);
|
||||
Assert.AreEqual(116999, difficultyPoint.Time);
|
||||
Assert.AreEqual(0.75, difficultyPoint.SliderVelocity, 0.1);
|
||||
ClassicAssert.AreEqual(116999, difficultyPoint.Time);
|
||||
ClassicAssert.AreEqual(0.75, difficultyPoint.SliderVelocity, 0.1);
|
||||
|
||||
var soundPoint = controlPoints.SamplePointAt(0);
|
||||
Assert.AreEqual(956, soundPoint.Time);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(60, soundPoint.SampleVolume);
|
||||
ClassicAssert.AreEqual(956, soundPoint.Time);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
ClassicAssert.AreEqual(60, soundPoint.SampleVolume);
|
||||
|
||||
soundPoint = controlPoints.SamplePointAt(53373);
|
||||
Assert.AreEqual(53373, soundPoint.Time);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(60, soundPoint.SampleVolume);
|
||||
ClassicAssert.AreEqual(53373, soundPoint.Time);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
ClassicAssert.AreEqual(60, soundPoint.SampleVolume);
|
||||
|
||||
soundPoint = controlPoints.SamplePointAt(119637);
|
||||
Assert.AreEqual(119637, soundPoint.Time);
|
||||
Assert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
Assert.AreEqual(80, soundPoint.SampleVolume);
|
||||
ClassicAssert.AreEqual(119637, soundPoint.Time);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.BANK_SOFT, soundPoint.SampleBank);
|
||||
ClassicAssert.AreEqual(80, soundPoint.SampleVolume);
|
||||
|
||||
var effectPoint = controlPoints.EffectPointAt(0);
|
||||
Assert.AreEqual(0, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
ClassicAssert.AreEqual(0, effectPoint.Time);
|
||||
ClassicAssert.False(effectPoint.KiaiMode);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(53703);
|
||||
Assert.AreEqual(53703, effectPoint.Time);
|
||||
Assert.IsTrue(effectPoint.KiaiMode);
|
||||
ClassicAssert.AreEqual(53703, effectPoint.Time);
|
||||
ClassicAssert.True(effectPoint.KiaiMode);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(116637);
|
||||
Assert.AreEqual(95901, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
ClassicAssert.AreEqual(95901, effectPoint.Time);
|
||||
ClassicAssert.False(effectPoint.KiaiMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,9 +398,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new Color4(255, 177, 140, 255),
|
||||
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
||||
};
|
||||
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||
ClassicAssert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||
for (int i = 0; i < expectedColors.Length; i++)
|
||||
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
||||
ClassicAssert.AreEqual(expectedColors[i], comboColors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,9 +427,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new Color4(100, 100, 100, 255),
|
||||
new Color4(142, 199, 255, 255),
|
||||
};
|
||||
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||
ClassicAssert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||
for (int i = 0; i < expectedColors.Length; i++)
|
||||
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
||||
ClassicAssert.AreEqual(expectedColors[i], comboColors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,12 +465,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new OsuBeatmapProcessor(converted).PreProcess();
|
||||
new OsuBeatmapProcessor(converted).PostProcess();
|
||||
|
||||
Assert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,12 +488,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new CatchBeatmapProcessor(converted).PreProcess();
|
||||
new CatchBeatmapProcessor(converted).PostProcess();
|
||||
|
||||
Assert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets);
|
||||
Assert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(1, ((IHasComboInformation)converted.HitObjects.ElementAt(0)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(2, ((IHasComboInformation)converted.HitObjects.ElementAt(2)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(3, ((IHasComboInformation)converted.HitObjects.ElementAt(4)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(4, ((IHasComboInformation)converted.HitObjects.ElementAt(6)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(8, ((IHasComboInformation)converted.HitObjects.ElementAt(8)).ComboIndexWithOffsets);
|
||||
ClassicAssert.AreEqual(9, ((IHasComboInformation)converted.HitObjects.ElementAt(11)).ComboIndexWithOffsets);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,8 +509,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256, 256), positionData!.Position);
|
||||
ClassicAssert.NotNull(positionData);
|
||||
ClassicAssert.AreEqual(new Vector2(256, 256), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,8 +526,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position);
|
||||
ClassicAssert.NotNull(positionData);
|
||||
ClassicAssert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,18 +544,18 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var curveData = hitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData!.Position);
|
||||
Assert.AreEqual(956, hitObjects[0].StartTime);
|
||||
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
ClassicAssert.NotNull(positionData);
|
||||
ClassicAssert.NotNull(curveData);
|
||||
ClassicAssert.AreEqual(new Vector2(192, 168), positionData!.Position);
|
||||
ClassicAssert.AreEqual(956, hitObjects[0].StartTime);
|
||||
ClassicAssert.True(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
positionData = hitObjects[1] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData!.Position);
|
||||
Assert.AreEqual(1285, hitObjects[1].StartTime);
|
||||
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
ClassicAssert.NotNull(positionData);
|
||||
ClassicAssert.AreEqual(new Vector2(304, 56), positionData!.Position);
|
||||
ClassicAssert.AreEqual(1285, hitObjects[1].StartTime);
|
||||
ClassicAssert.True(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,22 +586,22 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
|
||||
|
||||
// The fourth object is a slider.
|
||||
// `Samples` of a slider are presumed to control the volume of sounds that last the entire duration of the slider
|
||||
// (such as ticks, slider slide sounds, etc.)
|
||||
// Thus, the point of query of control points used for `Samples` is just beyond the start time of the slider.
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal11", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/soft-hitnormal11", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
|
||||
|
||||
// That said, the `NodeSamples` of the slider are responsible for the sounds of the slider's head / tail / repeats / large ticks etc.
|
||||
// Therefore, they should be read at the time instant correspondent to the given node.
|
||||
// This means that the tail should use bank 8 rather than 11.
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal11", ((ConvertSlider)hitObjects[4]).NodeSamples[0][0].LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/soft-hitnormal8", ((ConvertSlider)hitObjects[4]).NodeSamples[1][0].LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/soft-hitnormal11", ((ConvertSlider)hitObjects[4]).NodeSamples[0][0].LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/soft-hitnormal8", ((ConvertSlider)hitObjects[4]).NodeSamples[1][0].LookupNames.First());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
@@ -616,9 +617,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
@@ -634,11 +635,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
|
||||
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
|
||||
ClassicAssert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
|
||||
ClassicAssert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
|
||||
ClassicAssert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
|
||||
}
|
||||
|
||||
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.Samples[0];
|
||||
@@ -656,35 +657,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var slider1 = (ConvertSlider)hitObjects[0];
|
||||
|
||||
Assert.AreEqual(1, slider1.NodeSamples[0].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
|
||||
Assert.AreEqual(1, slider1.NodeSamples[1].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
|
||||
Assert.AreEqual(1, slider1.NodeSamples[2].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
|
||||
ClassicAssert.AreEqual(1, slider1.NodeSamples[0].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name);
|
||||
ClassicAssert.AreEqual(1, slider1.NodeSamples[1].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name);
|
||||
ClassicAssert.AreEqual(1, slider1.NodeSamples[2].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name);
|
||||
|
||||
var slider2 = (ConvertSlider)hitObjects[1];
|
||||
|
||||
Assert.AreEqual(2, slider2.NodeSamples[0].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
|
||||
Assert.AreEqual(2, slider2.NodeSamples[1].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
|
||||
Assert.AreEqual(2, slider2.NodeSamples[2].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
|
||||
ClassicAssert.AreEqual(2, slider2.NodeSamples[0].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name);
|
||||
ClassicAssert.AreEqual(2, slider2.NodeSamples[1].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name);
|
||||
ClassicAssert.AreEqual(2, slider2.NodeSamples[2].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name);
|
||||
|
||||
var slider3 = (ConvertSlider)hitObjects[2];
|
||||
|
||||
Assert.AreEqual(2, slider3.NodeSamples[0].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
|
||||
Assert.AreEqual(1, slider3.NodeSamples[1].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
|
||||
Assert.AreEqual(2, slider3.NodeSamples[2].Count);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
|
||||
Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
|
||||
ClassicAssert.AreEqual(2, slider3.NodeSamples[0].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name);
|
||||
ClassicAssert.AreEqual(1, slider3.NodeSamples[1].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name);
|
||||
ClassicAssert.AreEqual(2, slider3.NodeSamples[2].Count);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name);
|
||||
ClassicAssert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -698,7 +699,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
Assert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
|
||||
ClassicAssert.AreEqual(hitObjects[0].Samples[0].Bank, hitObjects[0].Samples[1].Bank);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -739,10 +740,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
static void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null)
|
||||
{
|
||||
Assert.AreEqual(normalBank, hitObject.Samples[0].Bank);
|
||||
ClassicAssert.AreEqual(normalBank, hitObject.Samples[0].Bank);
|
||||
|
||||
if (additionsBank != null)
|
||||
Assert.AreEqual(additionsBank, hitObject.Samples[1].Bank);
|
||||
ClassicAssert.AreEqual(additionsBank, hitObject.Samples[1].Bank);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,11 +757,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
|
||||
Assert.IsNotNull(beatmap);
|
||||
Assert.AreEqual("Beatmap with corrupted header", beatmap.Metadata.Title);
|
||||
Assert.AreEqual("Evil Hacker", beatmap.Metadata.Author.Username);
|
||||
ClassicAssert.NotNull(beatmap);
|
||||
ClassicAssert.AreEqual("Beatmap with corrupted header", beatmap.Metadata.Title);
|
||||
ClassicAssert.AreEqual("Evil Hacker", beatmap.Metadata.Author.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -774,11 +775,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
|
||||
Assert.IsNotNull(beatmap);
|
||||
Assert.AreEqual("Beatmap with no header", beatmap.Metadata.Title);
|
||||
Assert.AreEqual("Incredibly Evil Hacker", beatmap.Metadata.Author.Username);
|
||||
ClassicAssert.NotNull(beatmap);
|
||||
ClassicAssert.AreEqual("Beatmap with no header", beatmap.Metadata.Title);
|
||||
ClassicAssert.AreEqual("Incredibly Evil Hacker", beatmap.Metadata.Author.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,11 +793,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
|
||||
Assert.IsNotNull(beatmap);
|
||||
Assert.AreEqual("Empty lines at start", beatmap.Metadata.Title);
|
||||
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.Author.Username);
|
||||
ClassicAssert.NotNull(beatmap);
|
||||
ClassicAssert.AreEqual("Empty lines at start", beatmap.Metadata.Title);
|
||||
ClassicAssert.AreEqual("Edge Case Hunter", beatmap.Metadata.Author.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,11 +811,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
|
||||
Assert.IsNotNull(beatmap);
|
||||
Assert.AreEqual("The dog ate the file header", beatmap.Metadata.Title);
|
||||
Assert.AreEqual("Why does this keep happening", beatmap.Metadata.Author.Username);
|
||||
ClassicAssert.NotNull(beatmap);
|
||||
ClassicAssert.AreEqual("The dog ate the file header", beatmap.Metadata.Title);
|
||||
ClassicAssert.AreEqual("Why does this keep happening", beatmap.Metadata.Author.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,11 +829,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
|
||||
Assert.IsNotNull(beatmap);
|
||||
Assert.AreEqual("No empty line delimiting header from contents", beatmap.Metadata.Title);
|
||||
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.Author.Username);
|
||||
ClassicAssert.NotNull(beatmap);
|
||||
ClassicAssert.AreEqual("No empty line delimiting header from contents", beatmap.Metadata.Title);
|
||||
ClassicAssert.AreEqual("Edge Case Hunter", beatmap.Metadata.Author.Username);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -855,7 +856,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
|
||||
}
|
||||
|
||||
Assert.DoesNotThrow(LegacyDifficultyCalculatorBeatmapDecoder.Register);
|
||||
@@ -864,7 +865,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
|
||||
Assert.IsInstanceOf<LegacyDifficultyCalculatorBeatmapDecoder>(decoder);
|
||||
ClassicAssert.IsInstanceOf<LegacyDifficultyCalculatorBeatmapDecoder>(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.IO.Stores;
|
||||
@@ -100,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
// emulate non-legacy control points by cloning the non-legacy portion.
|
||||
// the assertion is that the encoder can recreate this losslessly from hitobject data.
|
||||
Assert.IsInstanceOf<LegacyControlPointInfo>(controlPointInfo);
|
||||
ClassicAssert.IsInstanceOf<LegacyControlPointInfo>(controlPointInfo);
|
||||
|
||||
var newControlPoints = new ControlPointInfo();
|
||||
|
||||
@@ -129,7 +130,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(actual.beatmap.HitObjects.Serialize(), Is.EqualTo(expected.beatmap.HitObjects.Serialize()));
|
||||
|
||||
// Check skin.
|
||||
Assert.IsTrue(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
||||
ClassicAssert.True(areComboColoursEqual(expected.skin.Configuration, actual.skin.Configuration));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -58,18 +59,18 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var score = decoder.Parse(resourceStream);
|
||||
|
||||
Assert.AreEqual(3, score.ScoreInfo.Ruleset.OnlineID);
|
||||
ClassicAssert.AreEqual(3, score.ScoreInfo.Ruleset.OnlineID);
|
||||
|
||||
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||
ClassicAssert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
ClassicAssert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
|
||||
|
||||
Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
|
||||
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||
ClassicAssert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
|
||||
ClassicAssert.AreEqual(3, score.ScoreInfo.MaxCombo);
|
||||
|
||||
Assert.That(score.ScoreInfo.APIMods.Select(m => m.Acronym), Is.EquivalentTo(new[] { "CL", "9K", "DS" }));
|
||||
|
||||
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
|
||||
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||
ClassicAssert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
|
||||
|
||||
Assert.That(score.Replay.Frames, Has.One.Matches<ManiaReplayFrame>(frame =>
|
||||
frame.Time == 414 && frame.Actions.SequenceEqual(new[] { ManiaAction.Key1, ManiaAction.Key18 })));
|
||||
@@ -85,10 +86,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var score = decoder.Parse(resourceStream);
|
||||
|
||||
Assert.AreEqual(1, score.ScoreInfo.Ruleset.OnlineID);
|
||||
Assert.AreEqual(4, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.LargeBonus]);
|
||||
Assert.AreEqual(4, score.ScoreInfo.MaxCombo);
|
||||
ClassicAssert.AreEqual(1, score.ScoreInfo.Ruleset.OnlineID);
|
||||
ClassicAssert.AreEqual(4, score.ScoreInfo.Statistics[HitResult.Great]);
|
||||
ClassicAssert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.LargeBonus]);
|
||||
ClassicAssert.AreEqual(4, score.ScoreInfo.MaxCombo);
|
||||
|
||||
Assert.That(score.Replay.Frames, Is.Not.Empty);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -25,73 +26,73 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
Assert.IsTrue(storyboard.HasDrawable);
|
||||
Assert.AreEqual(6, storyboard.Layers.Count());
|
||||
ClassicAssert.True(storyboard.HasDrawable);
|
||||
ClassicAssert.AreEqual(6, storyboard.Layers.Count());
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.IsNotNull(background);
|
||||
Assert.AreEqual(16, background.Elements.Count);
|
||||
Assert.IsTrue(background.VisibleWhenFailing);
|
||||
Assert.IsTrue(background.VisibleWhenPassing);
|
||||
Assert.AreEqual("Background", background.Name);
|
||||
ClassicAssert.NotNull(background);
|
||||
ClassicAssert.AreEqual(16, background.Elements.Count);
|
||||
ClassicAssert.True(background.VisibleWhenFailing);
|
||||
ClassicAssert.True(background.VisibleWhenPassing);
|
||||
ClassicAssert.AreEqual("Background", background.Name);
|
||||
|
||||
StoryboardLayer fail = storyboard.Layers.Single(l => l.Depth == 2);
|
||||
Assert.IsNotNull(fail);
|
||||
Assert.AreEqual(0, fail.Elements.Count);
|
||||
Assert.IsTrue(fail.VisibleWhenFailing);
|
||||
Assert.IsFalse(fail.VisibleWhenPassing);
|
||||
Assert.AreEqual("Fail", fail.Name);
|
||||
ClassicAssert.NotNull(fail);
|
||||
ClassicAssert.AreEqual(0, fail.Elements.Count);
|
||||
ClassicAssert.True(fail.VisibleWhenFailing);
|
||||
ClassicAssert.False(fail.VisibleWhenPassing);
|
||||
ClassicAssert.AreEqual("Fail", fail.Name);
|
||||
|
||||
StoryboardLayer pass = storyboard.Layers.Single(l => l.Depth == 1);
|
||||
Assert.IsNotNull(pass);
|
||||
Assert.AreEqual(0, pass.Elements.Count);
|
||||
Assert.IsFalse(pass.VisibleWhenFailing);
|
||||
Assert.IsTrue(pass.VisibleWhenPassing);
|
||||
Assert.AreEqual("Pass", pass.Name);
|
||||
ClassicAssert.NotNull(pass);
|
||||
ClassicAssert.AreEqual(0, pass.Elements.Count);
|
||||
ClassicAssert.False(pass.VisibleWhenFailing);
|
||||
ClassicAssert.True(pass.VisibleWhenPassing);
|
||||
ClassicAssert.AreEqual("Pass", pass.Name);
|
||||
|
||||
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
|
||||
Assert.IsNotNull(foreground);
|
||||
Assert.AreEqual(151, foreground.Elements.Count);
|
||||
Assert.IsTrue(foreground.VisibleWhenFailing);
|
||||
Assert.IsTrue(foreground.VisibleWhenPassing);
|
||||
Assert.AreEqual("Foreground", foreground.Name);
|
||||
ClassicAssert.NotNull(foreground);
|
||||
ClassicAssert.AreEqual(151, foreground.Elements.Count);
|
||||
ClassicAssert.True(foreground.VisibleWhenFailing);
|
||||
ClassicAssert.True(foreground.VisibleWhenPassing);
|
||||
ClassicAssert.AreEqual("Foreground", foreground.Name);
|
||||
|
||||
StoryboardLayer overlay = storyboard.Layers.Single(l => l.Depth == int.MinValue);
|
||||
Assert.IsNotNull(overlay);
|
||||
Assert.IsEmpty(overlay.Elements);
|
||||
Assert.IsTrue(overlay.VisibleWhenFailing);
|
||||
Assert.IsTrue(overlay.VisibleWhenPassing);
|
||||
Assert.AreEqual("Overlay", overlay.Name);
|
||||
ClassicAssert.NotNull(overlay);
|
||||
ClassicAssert.IsEmpty(overlay.Elements);
|
||||
ClassicAssert.True(overlay.VisibleWhenFailing);
|
||||
ClassicAssert.True(overlay.VisibleWhenPassing);
|
||||
ClassicAssert.AreEqual("Overlay", overlay.Name);
|
||||
|
||||
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
|
||||
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
|
||||
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
|
||||
|
||||
Assert.AreEqual(15, spriteCount);
|
||||
Assert.AreEqual(1, animationCount);
|
||||
Assert.AreEqual(0, sampleCount);
|
||||
Assert.AreEqual(background.Elements.Count, spriteCount + animationCount + sampleCount);
|
||||
ClassicAssert.AreEqual(15, spriteCount);
|
||||
ClassicAssert.AreEqual(1, animationCount);
|
||||
ClassicAssert.AreEqual(0, sampleCount);
|
||||
ClassicAssert.AreEqual(background.Elements.Count, spriteCount + animationCount + sampleCount);
|
||||
|
||||
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
|
||||
Assert.NotNull(sprite);
|
||||
Assert.IsTrue(sprite!.HasCommands);
|
||||
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
||||
Assert.IsTrue(sprite.IsDrawable);
|
||||
Assert.AreEqual(Anchor.Centre, sprite.Origin);
|
||||
Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
|
||||
ClassicAssert.NotNull(sprite);
|
||||
ClassicAssert.True(sprite!.HasCommands);
|
||||
ClassicAssert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
||||
ClassicAssert.True(sprite.IsDrawable);
|
||||
ClassicAssert.AreEqual(Anchor.Centre, sprite.Origin);
|
||||
ClassicAssert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
|
||||
|
||||
var animation = background.Elements.OfType<StoryboardAnimation>().First();
|
||||
Assert.NotNull(animation);
|
||||
Assert.AreEqual(141175, animation.EndTime);
|
||||
Assert.AreEqual(10, animation.FrameCount);
|
||||
Assert.AreEqual(30, animation.FrameDelay);
|
||||
Assert.IsTrue(animation.HasCommands);
|
||||
Assert.AreEqual(new Vector2(320, 240), animation.InitialPosition);
|
||||
Assert.IsTrue(animation.IsDrawable);
|
||||
Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
|
||||
Assert.AreEqual(Anchor.Centre, animation.Origin);
|
||||
Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
|
||||
Assert.AreEqual(78993, animation.StartTime);
|
||||
ClassicAssert.NotNull(animation);
|
||||
ClassicAssert.AreEqual(141175, animation.EndTime);
|
||||
ClassicAssert.AreEqual(10, animation.FrameCount);
|
||||
ClassicAssert.AreEqual(30, animation.FrameDelay);
|
||||
ClassicAssert.True(animation.HasCommands);
|
||||
ClassicAssert.AreEqual(new Vector2(320, 240), animation.InitialPosition);
|
||||
ClassicAssert.True(animation.IsDrawable);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
|
||||
ClassicAssert.AreEqual(Anchor.Centre, animation.Origin);
|
||||
ClassicAssert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
|
||||
ClassicAssert.AreEqual(78993, animation.StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,13 +107,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(1, background.Elements.Count);
|
||||
ClassicAssert.AreEqual(1, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||
ClassicAssert.AreEqual(2000, background.Elements[0].StartTime);
|
||||
ClassicAssert.AreEqual(2000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||
|
||||
Assert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
|
||||
Assert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
|
||||
ClassicAssert.AreEqual(3000, (background.Elements[0] as StoryboardAnimation)?.GetEndTime());
|
||||
ClassicAssert.AreEqual(12000, (background.Elements[0] as StoryboardAnimation)?.EndTimeForDisplay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,11 +128,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(1, background.Elements.Count);
|
||||
ClassicAssert.AreEqual(1, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(2000, background.Elements[0].StartTime);
|
||||
ClassicAssert.AreEqual(2000, background.Elements[0].StartTime);
|
||||
// This property should be used in DrawableStoryboardAnimation as a starting point for animation playback.
|
||||
Assert.AreEqual(1000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||
ClassicAssert.AreEqual(1000, (background.Elements[0] as StoryboardAnimation)?.EarliestTransformTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,10 +147,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(2, background.Elements.Count);
|
||||
ClassicAssert.AreEqual(2, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(1500, background.Elements[1].StartTime);
|
||||
ClassicAssert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
ClassicAssert.AreEqual(1500, background.Elements[1].StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,12 +165,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(2, background.Elements.Count);
|
||||
ClassicAssert.AreEqual(2, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(1000, background.Elements[1].StartTime);
|
||||
ClassicAssert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
ClassicAssert.AreEqual(1000, background.Elements[1].StartTime);
|
||||
|
||||
Assert.AreEqual(1000, storyboard.EarliestEventTime);
|
||||
ClassicAssert.AreEqual(1000, storyboard.EarliestEventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,12 +185,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(2, background.Elements.Count);
|
||||
ClassicAssert.AreEqual(2, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(1000, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(1000, background.Elements[1].StartTime);
|
||||
ClassicAssert.AreEqual(1000, background.Elements[0].StartTime);
|
||||
ClassicAssert.AreEqual(1000, background.Elements[1].StartTime);
|
||||
|
||||
Assert.AreEqual(1000, storyboard.EarliestEventTime);
|
||||
ClassicAssert.AreEqual(1000, storyboard.EarliestEventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +205,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X);
|
||||
ClassicAssert.AreEqual(3456, ((StoryboardSprite)background.Elements.Single()).InitialPosition.X);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +222,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.AreEqual("Video.avi", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
ClassicAssert.AreEqual("Video.avi", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,7 +239,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.AreEqual("Video.AVI", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
ClassicAssert.AreEqual("Video.AVI", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,12 +269,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
|
||||
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[0]).LoopType);
|
||||
Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[1]).LoopType);
|
||||
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[2]).LoopType);
|
||||
Assert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[3]).LoopType);
|
||||
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[4]).LoopType);
|
||||
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[0]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[1]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[2]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopOnce, ((StoryboardAnimation)foreground.Elements[3]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[4]).LoopType);
|
||||
ClassicAssert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DeepEqual.Syntax;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -15,7 +15,6 @@ using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
|
||||
@@ -33,17 +32,17 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
var meta = beatmap.BeatmapInfo.Metadata;
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
|
||||
Assert.AreEqual("Soleily", meta.Artist);
|
||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
Assert.AreEqual("Gamu", meta.Author.Username);
|
||||
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
||||
Assert.AreEqual(164471, meta.PreviewTime);
|
||||
Assert.AreEqual(string.Empty, meta.Source);
|
||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
||||
Assert.AreEqual("Renatus", meta.Title);
|
||||
Assert.AreEqual("Renatus", meta.TitleUnicode);
|
||||
ClassicAssert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
|
||||
ClassicAssert.AreEqual("Soleily", meta.Artist);
|
||||
ClassicAssert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
ClassicAssert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
ClassicAssert.AreEqual("Gamu", meta.Author.Username);
|
||||
ClassicAssert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
||||
ClassicAssert.AreEqual(164471, meta.PreviewTime);
|
||||
ClassicAssert.AreEqual(string.Empty, meta.Source);
|
||||
ClassicAssert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
||||
ClassicAssert.AreEqual("Renatus", meta.Title);
|
||||
ClassicAssert.AreEqual("Renatus", meta.TitleUnicode);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -51,14 +50,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
var beatmapInfo = beatmap.BeatmapInfo;
|
||||
Assert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
Assert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
Assert.AreEqual(false, beatmap.SpecialStyle);
|
||||
Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
Assert.AreEqual(false, beatmap.LetterboxInBreaks);
|
||||
Assert.AreEqual(false, beatmap.WidescreenStoryboard);
|
||||
Assert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
Assert.AreEqual(0, beatmap.CountdownOffset);
|
||||
ClassicAssert.AreEqual(0, beatmap.AudioLeadIn);
|
||||
ClassicAssert.AreEqual(0.7f, beatmap.StackLeniency);
|
||||
ClassicAssert.AreEqual(false, beatmap.SpecialStyle);
|
||||
ClassicAssert.True(beatmapInfo.Ruleset.OnlineID == 0);
|
||||
ClassicAssert.AreEqual(false, beatmap.LetterboxInBreaks);
|
||||
ClassicAssert.AreEqual(false, beatmap.WidescreenStoryboard);
|
||||
ClassicAssert.AreEqual(CountdownType.None, beatmap.Countdown);
|
||||
ClassicAssert.AreEqual(0, beatmap.CountdownOffset);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -73,13 +72,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
95901, 106450, 116999, 119637, 130186, 140735, 151285,
|
||||
161834, 164471, 175020, 185570, 196119, 206669, 209306
|
||||
};
|
||||
Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
ClassicAssert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length);
|
||||
for (int i = 0; i < expectedBookmarks.Length; i++)
|
||||
Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
Assert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
Assert.AreEqual(4, beatmapInfo.BeatDivisor);
|
||||
Assert.AreEqual(4, beatmap.GridSize);
|
||||
Assert.AreEqual(2, beatmap.TimelineZoom);
|
||||
ClassicAssert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]);
|
||||
ClassicAssert.AreEqual(1.8, beatmap.DistanceSpacing);
|
||||
ClassicAssert.AreEqual(4, beatmapInfo.BeatDivisor);
|
||||
ClassicAssert.AreEqual(4, beatmap.GridSize);
|
||||
ClassicAssert.AreEqual(2, beatmap.TimelineZoom);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -87,12 +86,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var beatmap = decodeAsJson(normal);
|
||||
var difficulty = beatmap.Difficulty;
|
||||
Assert.AreEqual(6.5f, difficulty.DrainRate);
|
||||
Assert.AreEqual(4, difficulty.CircleSize);
|
||||
Assert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
Assert.AreEqual(9, difficulty.ApproachRate);
|
||||
Assert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
Assert.AreEqual(2, difficulty.SliderTickRate);
|
||||
ClassicAssert.AreEqual(6.5f, difficulty.DrainRate);
|
||||
ClassicAssert.AreEqual(4, difficulty.CircleSize);
|
||||
ClassicAssert.AreEqual(8, difficulty.OverallDifficulty);
|
||||
ClassicAssert.AreEqual(9, difficulty.ApproachRate);
|
||||
ClassicAssert.AreEqual(1.8, difficulty.SliderMultiplier);
|
||||
ClassicAssert.AreEqual(2, difficulty.SliderTickRate);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -112,19 +111,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = beatmap.HitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(90, curveData.Path.Distance);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
Assert.That(positionData, Is.Not.Null);
|
||||
Assert.That(curveData, Is.Not.Null);
|
||||
ClassicAssert.AreEqual(90, curveData.Path.Distance);
|
||||
ClassicAssert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
ClassicAssert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
ClassicAssert.True(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
positionData = beatmap.HitObjects[1] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
Assert.That(positionData, Is.Not.Null);
|
||||
ClassicAssert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
ClassicAssert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
||||
ClassicAssert.True(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -135,35 +134,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats;
|
||||
var positionData = beatmap.HitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(90, curveData.Path.Distance);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
Assert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
Assert.That(positionData, Is.Not.Null);
|
||||
Assert.That(curveData, Is.Not.Null);
|
||||
ClassicAssert.AreEqual(90, curveData.Path.Distance);
|
||||
ClassicAssert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
ClassicAssert.AreEqual(956, beatmap.HitObjects[0].StartTime);
|
||||
ClassicAssert.True(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
positionData = beatmap.HitObjects[1] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
||||
Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
Assert.That(positionData, Is.Not.Null);
|
||||
ClassicAssert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
ClassicAssert.AreEqual(1285, beatmap.HitObjects[1].StartTime);
|
||||
ClassicAssert.True(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
}
|
||||
|
||||
[TestCase(normal)]
|
||||
[TestCase(marathon)]
|
||||
[Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")]
|
||||
// Currently fails:
|
||||
// [TestCase(with_sb)]
|
||||
public void TestParity(string beatmap)
|
||||
{
|
||||
var legacy = decode(beatmap, out Beatmap json);
|
||||
json.WithDeepEqual(legacy)
|
||||
.IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
|
||||
// Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
|
||||
|| r.Name == nameof(LegacyDecoder<Beatmap>.LegacySampleControlPoint.CustomSampleBank))
|
||||
.Assert();
|
||||
}
|
||||
// [TestCase(normal)]
|
||||
// [TestCase(marathon)]
|
||||
// [Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")]
|
||||
// // Currently fails:
|
||||
// // [TestCase(with_sb)]
|
||||
// public void TestParity(string beatmap)
|
||||
// {
|
||||
// var legacy = decode(beatmap, out Beatmap json);
|
||||
// json.WithDeepEqual(legacy)
|
||||
// .IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)
|
||||
// // Todo: CustomSampleBank shouldn't exist going forward, we need a conversion mechanism
|
||||
// || r.Name == nameof(LegacyDecoder<Beatmap>.LegacySampleControlPoint.CustomSampleBank))
|
||||
// .Assert();
|
||||
// }
|
||||
|
||||
[Test]
|
||||
public void TestGetJsonDecoder()
|
||||
@@ -187,7 +186,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
Assert.IsInstanceOf(typeof(JsonBeatmapDecoder), decoder);
|
||||
ClassicAssert.IsInstanceOf(typeof(JsonBeatmapDecoder), decoder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.Formats
|
||||
@@ -33,9 +34,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[TestCase(-10, 10)]
|
||||
public void TestValidRanges(double input, double limit = Parsing.MAX_PARSE_VALUE)
|
||||
{
|
||||
Assert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input);
|
||||
Assert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input);
|
||||
Assert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input);
|
||||
ClassicAssert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input);
|
||||
ClassicAssert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input);
|
||||
ClassicAssert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input);
|
||||
}
|
||||
|
||||
[TestCase(double.PositiveInfinity)]
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
@@ -62,16 +62,16 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
// TODO: add back some extra checks outside of the realm ones?
|
||||
// var set = queryBeatmapSets().First();
|
||||
// foreach (BeatmapInfo b in set.Beatmaps)
|
||||
// Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||
// Assert.IsTrue(set.Beatmaps.Count > 0);
|
||||
// ClassicAssert.True(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||
// ClassicAssert.True(set.Beatmaps.Count > 0);
|
||||
// var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
|
||||
// Assert.IsTrue(beatmap?.HitObjects.Any() == true);
|
||||
// ClassicAssert.True(beatmap?.HitObjects.Any() == true);
|
||||
// beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 1))?.Beatmap;
|
||||
// Assert.IsTrue(beatmap?.HitObjects.Any() == true);
|
||||
// ClassicAssert.True(beatmap?.HitObjects.Any() == true);
|
||||
// beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 2))?.Beatmap;
|
||||
// Assert.IsTrue(beatmap?.HitObjects.Any() == true);
|
||||
// ClassicAssert.True(beatmap?.HitObjects.Any() == true);
|
||||
// beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
|
||||
// Assert.IsTrue(beatmap?.HitObjects.Any() == true);
|
||||
// ClassicAssert.True(beatmap?.HitObjects.Any() == true);
|
||||
}
|
||||
|
||||
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
|
||||
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
while (!result()) Thread.Sleep(200);
|
||||
});
|
||||
|
||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||
ClassicAssert.True(task.Wait(timeout), failureMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.IO;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.IO
|
||||
@@ -20,10 +21,10 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual("line 1", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("line 2", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("line 3", bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 1", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 2", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 3", bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +36,11 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual("line 1", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("peek this", bufferedReader.PeekLine());
|
||||
Assert.AreEqual("peek this", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("line 3", bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 1", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("peek this", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("peek this", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 3", bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,14 +52,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual("peek this once", bufferedReader.PeekLine());
|
||||
Assert.AreEqual("peek this once", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("line 2", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
Assert.AreEqual("peek this a lot", bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("peek this once", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("peek this once", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("line 2", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("peek this a lot", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("peek this a lot", bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,11 +71,11 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual("first line", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("second line", bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.PeekLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("first line", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("second line", bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.PeekLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.PeekLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +85,10 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream())
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.IsNull(bufferedReader.PeekLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.ReadLine());
|
||||
Assert.IsNull(bufferedReader.PeekLine());
|
||||
ClassicAssert.Null(bufferedReader.PeekLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.ReadLine());
|
||||
ClassicAssert.Null(bufferedReader.PeekLine());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +100,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual(contents, bufferedReader.ReadToEnd());
|
||||
ClassicAssert.AreEqual(contents, bufferedReader.ReadToEnd());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,14 +112,14 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
|
||||
using (var bufferedReader = new LineBufferedReader(stream))
|
||||
{
|
||||
Assert.AreEqual("this line is gone", bufferedReader.ReadLine());
|
||||
Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine());
|
||||
ClassicAssert.AreEqual("this line is gone", bufferedReader.ReadLine());
|
||||
ClassicAssert.AreEqual("this one shouldn't be", bufferedReader.PeekLine());
|
||||
|
||||
string[] endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.AreEqual(3, endingLines.Length);
|
||||
Assert.AreEqual("this one shouldn't be", endingLines[0]);
|
||||
Assert.AreEqual("these ones", endingLines[1]);
|
||||
Assert.AreEqual("definitely not", endingLines[2]);
|
||||
ClassicAssert.AreEqual(3, endingLines.Length);
|
||||
ClassicAssert.AreEqual("this one shouldn't be", endingLines[0]);
|
||||
ClassicAssert.AreEqual("these ones", endingLines[1]);
|
||||
ClassicAssert.AreEqual("definitely not", endingLines[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
@@ -38,7 +39,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
};
|
||||
string[] maps = reader.Filenames.ToArray();
|
||||
foreach (string map in expected)
|
||||
Assert.Contains(map, maps);
|
||||
ClassicAssert.Contains(map, maps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,17 +57,17 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var meta = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
|
||||
Assert.AreEqual("Soleily", meta.Artist);
|
||||
Assert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
Assert.AreEqual("Deif", meta.Author.Username);
|
||||
Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
||||
Assert.AreEqual(164471, meta.PreviewTime);
|
||||
Assert.AreEqual(string.Empty, meta.Source);
|
||||
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
||||
Assert.AreEqual("Renatus", meta.Title);
|
||||
Assert.AreEqual("Renatus", meta.TitleUnicode);
|
||||
ClassicAssert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet?.OnlineID);
|
||||
ClassicAssert.AreEqual("Soleily", meta.Artist);
|
||||
ClassicAssert.AreEqual("Soleily", meta.ArtistUnicode);
|
||||
ClassicAssert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);
|
||||
ClassicAssert.AreEqual("Deif", meta.Author.Username);
|
||||
ClassicAssert.AreEqual("machinetop_background.jpg", meta.BackgroundFile);
|
||||
ClassicAssert.AreEqual(164471, meta.PreviewTime);
|
||||
ClassicAssert.AreEqual(string.Empty, meta.Source);
|
||||
ClassicAssert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags);
|
||||
ClassicAssert.AreEqual("Renatus", meta.Title);
|
||||
ClassicAssert.AreEqual("Renatus", meta.TitleUnicode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
|
||||
{
|
||||
Assert.AreEqual("osu file format v13", stream.ReadLine()?.Trim());
|
||||
ClassicAssert.AreEqual("osu file format v13", stream.ReadLine()?.Trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
@@ -225,7 +226,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
{
|
||||
var actualBracket = StarDifficulty.GetDifficultyRating(starRating);
|
||||
|
||||
Assert.AreEqual(expectedBracket, actualBracket);
|
||||
ClassicAssert.AreEqual(expectedBracket, actualBracket);
|
||||
}
|
||||
|
||||
private partial class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -29,7 +30,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
working.ResetEvent.Set();
|
||||
|
||||
Assert.NotNull(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
|
||||
ClassicAssert.NotNull(working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -48,11 +49,11 @@ namespace osu.Game.Tests.Beatmaps
|
||||
loadCompleted.Set();
|
||||
}, TaskCreationOptions.LongRunning);
|
||||
|
||||
Assert.IsTrue(loadStarted.Wait(10000));
|
||||
ClassicAssert.True(loadStarted.Wait(10000));
|
||||
|
||||
cts.Cancel();
|
||||
|
||||
Assert.IsTrue(loadCompleted.Wait(10000));
|
||||
ClassicAssert.True(loadCompleted.Wait(10000));
|
||||
|
||||
working.ResetEvent.Set();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Tests.Chat
|
||||
@@ -31,8 +32,8 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a gopher://really-old-protocol we don't support." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(0, result.Links.Count);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(0, result.Links.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -40,8 +41,8 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a osunotarealprotocol://completely-made-up-protocol we don't support." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(0, result.Links.Count);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(0, result.Links.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -49,9 +50,9 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "forgotspacehttps://dev.ppy.sh joinmyosump://12345 jointheosu://chan/#english" });
|
||||
|
||||
Assert.AreEqual("https://dev.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual("osump://12345", result.Links[1].Url);
|
||||
Assert.AreEqual("osu://chan/#english", result.Links[2].Url);
|
||||
ClassicAssert.AreEqual("https://dev.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual("osump://12345", result.Links[1].Url);
|
||||
ClassicAssert.AreEqual("osu://chan/#english", result.Links[2].Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -59,11 +60,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://www.basic-link.com/?test=test." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(36, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(36, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[TestCase(LinkAction.OpenBeatmap, "456", "https://dev.ppy.sh/beatmapsets/123#osu/456")]
|
||||
@@ -79,12 +80,12 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = link });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual(expectedAction, result.Links[0].Action);
|
||||
Assert.AreEqual(expectedArg, result.Links[0].Argument);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual(expectedAction, result.Links[0].Action);
|
||||
ClassicAssert.AreEqual(expectedArg, result.Links[0].Argument);
|
||||
if (expectedAction == LinkAction.External)
|
||||
Assert.AreEqual(link, result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(link, result.Links[0].Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -95,20 +96,20 @@ namespace osu.Game.Tests.Chat
|
||||
Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/"
|
||||
});
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(3, result.Links.Count);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(3, result.Links.Count);
|
||||
|
||||
Assert.AreEqual("http://test.io/link#fragment", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(28, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("http://test.io/link#fragment", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(28, result.Links[0].Length);
|
||||
|
||||
Assert.AreEqual("https://twitter.com", result.Links[1].Url);
|
||||
Assert.AreEqual(45, result.Links[1].Index);
|
||||
Assert.AreEqual(19, result.Links[1].Length);
|
||||
ClassicAssert.AreEqual("https://twitter.com", result.Links[1].Url);
|
||||
ClassicAssert.AreEqual(45, result.Links[1].Index);
|
||||
ClassicAssert.AreEqual(19, result.Links[1].Length);
|
||||
|
||||
Assert.AreEqual("http://example.com/", result.Links[2].Url);
|
||||
Assert.AreEqual(108, result.Links[2].Index);
|
||||
Assert.AreEqual(19, result.Links[2].Length);
|
||||
ClassicAssert.AreEqual("http://example.com/", result.Links[2].Url);
|
||||
ClassicAssert.AreEqual(108, result.Links[2].Index);
|
||||
ClassicAssert.AreEqual(19, result.Links[2].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -116,10 +117,10 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "https://twitter.com/#!/hashbanglinks" });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(result.Content, result.Links[0].Url);
|
||||
Assert.AreEqual(0, result.Links[0].Index);
|
||||
Assert.AreEqual(36, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(result.Content, result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(0, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(36, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -127,10 +128,10 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "http://www.chiark.greenend.org.uk/~sgtatham/putty/" });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(result.Content, result.Links[0].Url);
|
||||
Assert.AreEqual(0, result.Links[0].Index);
|
||||
Assert.AreEqual(50, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(result.Content, result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(0, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(50, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -138,9 +139,9 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(6, result.Links[0].Index);
|
||||
Assert.AreEqual(39, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(6, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(39, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -148,11 +149,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]]." });
|
||||
|
||||
Assert.AreEqual("This is a Wiki Link.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(9, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a Wiki Link.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(9, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -160,20 +161,20 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]] [[Wiki:Link]][[Wiki.Link]]." });
|
||||
|
||||
Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent);
|
||||
Assert.AreEqual(3, result.Links.Count);
|
||||
ClassicAssert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(3, result.Links.Count);
|
||||
|
||||
Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(9, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(9, result.Links[0].Length);
|
||||
|
||||
Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
|
||||
Assert.AreEqual(20, result.Links[1].Index);
|
||||
Assert.AreEqual(9, result.Links[1].Length);
|
||||
ClassicAssert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
|
||||
ClassicAssert.AreEqual(20, result.Links[1].Index);
|
||||
ClassicAssert.AreEqual(9, result.Links[1].Length);
|
||||
|
||||
Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
|
||||
Assert.AreEqual(29, result.Links[2].Index);
|
||||
Assert.AreEqual(9, result.Links[2].Length);
|
||||
ClassicAssert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
|
||||
ClassicAssert.AreEqual(29, result.Links[2].Index);
|
||||
ClassicAssert.AreEqual(9, result.Links[2].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -181,11 +182,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh] of links." });
|
||||
|
||||
Assert.AreEqual("This is a simple test of links.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(11, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a simple test of links.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(11, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -193,11 +194,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (tricky (one))[https://osu.ppy.sh]!" });
|
||||
|
||||
Assert.AreEqual("This is a tricky (one)!", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(12, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a tricky (one)!", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(12, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -205,22 +206,22 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is (another loose bracket \\))[https://osu.ppy.sh]." });
|
||||
|
||||
Assert.AreEqual("This is another loose bracket ).", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(8, result.Links[0].Index);
|
||||
Assert.AreEqual(23, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is another loose bracket ).", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(8, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(23, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOldFormatWithBackslashes()
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link (should end with a backslash \\)[https://osu.ppy.sh]." });
|
||||
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(29, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(29, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -228,11 +229,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (\\)super\\(\\( tricky (one))[https://osu.ppy.sh]!" });
|
||||
|
||||
Assert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(21, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a )super(( tricky (one)!", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(21, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -240,11 +241,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh simple test]." });
|
||||
|
||||
Assert.AreEqual("This is a simple test.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(11, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a simple test.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(11, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -252,11 +253,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh nasty link with escaped brackets: \\] and \\[]" });
|
||||
|
||||
Assert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(41, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a nasty link with escaped brackets: ] and [", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(41, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -264,11 +265,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh link \\ with \\ backslashes \\]" });
|
||||
|
||||
Assert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(27, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a link \\ with \\ backslashes \\", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(27, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -276,11 +277,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [link [with \\] too many brackets \\[ ]]]" });
|
||||
|
||||
Assert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(36, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a [link [with ] too many brackets [ ]]", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(36, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -288,11 +289,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [simple test](https://osu.ppy.sh)." });
|
||||
|
||||
Assert.AreEqual("This is a simple test.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(11, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a simple test.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(11, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -300,11 +301,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [tricky [one]](https://osu.ppy.sh)!" });
|
||||
|
||||
Assert.AreEqual("This is a tricky [one]!", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(12, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a tricky [one]!", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(12, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -312,22 +313,22 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is [another loose bracket \\]](https://osu.ppy.sh)." });
|
||||
|
||||
Assert.AreEqual("This is another loose bracket ].", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(8, result.Links[0].Index);
|
||||
Assert.AreEqual(23, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is another loose bracket ].", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(8, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(23, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMarkdownFormatWithBackslashes()
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This link [should end with a backslash \\](https://osu.ppy.sh)." });
|
||||
Assert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(29, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This link should end with a backslash \\.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(29, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -335,11 +336,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [\\]super\\[\\[ tricky [one]](https://osu.ppy.sh)!" });
|
||||
|
||||
Assert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(21, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a ]super[[ tricky [one]!", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(21, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -347,11 +348,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"osu!\") before..." });
|
||||
|
||||
Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(15, result.Links[0].Index);
|
||||
Assert.AreEqual(16, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(16, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -359,11 +360,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [this link format](https://osu.ppy.sh \"inner quote \\\" just to confuse \") before..." });
|
||||
|
||||
Assert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(15, result.Links[0].Index);
|
||||
Assert.AreEqual(16, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("I haven't seen this link format before...", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(16, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -371,11 +372,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://osu.ppy.sh](https://osu.ppy.sh \"https://osu.ppy.sh\") before..." });
|
||||
|
||||
Assert.AreEqual("I haven't seen https://osu.ppy.sh before...", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(15, result.Links[0].Index);
|
||||
Assert.AreEqual(18, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("I haven't seen https://osu.ppy.sh before...", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(18, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -383,11 +384,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [oh no, text here! https://osu.ppy.sh](https://osu.ppy.sh) before..." });
|
||||
|
||||
Assert.AreEqual("I haven't seen oh no, text here! https://osu.ppy.sh before...", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(15, result.Links[0].Index);
|
||||
Assert.AreEqual(36, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("I haven't seen oh no, text here! https://osu.ppy.sh before...", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(36, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -395,11 +396,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "I haven't seen [https://google.com](https://osu.ppy.sh) before..." });
|
||||
|
||||
Assert.AreEqual("I haven't seen https://google.com before...", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(15, result.Links[0].Index);
|
||||
Assert.AreEqual(18, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("I haven't seen https://google.com before...", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(18, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -407,11 +408,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "super broken https://[osu.ppy](https://reddit.com).sh/" });
|
||||
|
||||
Assert.AreEqual("super broken https://osu.ppy.sh/", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://reddit.com", result.Links[0].Url);
|
||||
Assert.AreEqual(21, result.Links[0].Index);
|
||||
Assert.AreEqual(7, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("super broken https://osu.ppy.sh/", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://reddit.com", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(21, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(7, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -420,16 +421,16 @@ namespace osu.Game.Tests.Chat
|
||||
// the raw link has a port at the end of it, so that the raw link regex terminates at the port and doesn't consume display text from the formatted one
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "https://localhost:8080[https://osu.ppy.sh](https://osu.ppy.sh) should be two links" });
|
||||
|
||||
Assert.AreEqual("https://localhost:8080https://osu.ppy.sh should be two links", result.DisplayContent);
|
||||
Assert.AreEqual(2, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://localhost:8080https://osu.ppy.sh should be two links", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(2, result.Links.Count);
|
||||
|
||||
Assert.AreEqual("https://localhost:8080", result.Links[0].Url);
|
||||
Assert.AreEqual(0, result.Links[0].Index);
|
||||
Assert.AreEqual(22, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("https://localhost:8080", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(0, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(22, result.Links[0].Length);
|
||||
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[1].Url);
|
||||
Assert.AreEqual(22, result.Links[1].Index);
|
||||
Assert.AreEqual(18, result.Links[1].Length);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[1].Url);
|
||||
ClassicAssert.AreEqual(22, result.Links[1].Index);
|
||||
ClassicAssert.AreEqual(18, result.Links[1].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -437,10 +438,10 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is an #english and #japanese." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(2, result.Links.Count);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(2, result.Links.Count);
|
||||
ClassicAssert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#japanese", result.Links[1].Url);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -448,20 +449,20 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = $"This is a custom protocol {OsuGameBase.OSU_PROTOCOL}chan/#english." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual(26, result.Links[0].Index);
|
||||
Assert.AreEqual(19, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(26, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(19, result.Links[0].Length);
|
||||
|
||||
result = MessageFormatter.FormatMessage(new Message { Content = $"This is a [custom protocol]({OsuGameBase.OSU_PROTOCOL}chan/#english)." });
|
||||
|
||||
Assert.AreEqual("This is a custom protocol.", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
Assert.AreEqual("#english", result.Links[0].Argument);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(15, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a custom protocol.", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual($"{OsuGameBase.OSU_PROTOCOL}chan/#english", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual("#english", result.Links[0].Argument);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -469,11 +470,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "Join my multiplayer game osump://12346." });
|
||||
|
||||
Assert.AreEqual(result.Content, result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("osump://12346", result.Links[0].Url);
|
||||
Assert.AreEqual(25, result.Links[0].Index);
|
||||
Assert.AreEqual(13, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual(result.Content, result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("osump://12346", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(25, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(13, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -481,11 +482,11 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [[simple test]]]." });
|
||||
|
||||
Assert.AreEqual("This is a [[simple test]].", result.DisplayContent);
|
||||
Assert.AreEqual(1, result.Links.Count);
|
||||
Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
Assert.AreEqual(10, result.Links[0].Index);
|
||||
Assert.AreEqual(15, result.Links[0].Length);
|
||||
ClassicAssert.AreEqual("This is a [[simple test]].", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(1, result.Links.Count);
|
||||
ClassicAssert.AreEqual("https://osu.ppy.sh", result.Links[0].Url);
|
||||
ClassicAssert.AreEqual(10, result.Links[0].Index);
|
||||
ClassicAssert.AreEqual(15, result.Links[0].Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -496,44 +497,44 @@ namespace osu.Game.Tests.Chat
|
||||
Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12"
|
||||
});
|
||||
|
||||
Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now![emoji]", result.DisplayContent);
|
||||
Assert.AreEqual(4, result.Links.Count);
|
||||
ClassicAssert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now![emoji]", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(4, result.Links.Count);
|
||||
|
||||
Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links");
|
||||
Assert.That(f, Is.Not.Null);
|
||||
Assert.AreEqual(44, f.Index);
|
||||
Assert.AreEqual(10, f.Length);
|
||||
ClassicAssert.AreEqual(44, f.Index);
|
||||
ClassicAssert.AreEqual(10, f.Length);
|
||||
|
||||
f = result.Links.Find(l => l.Url == "http://www.simple-test.com");
|
||||
Assert.That(f, Is.Not.Null);
|
||||
Assert.AreEqual(10, f.Index);
|
||||
Assert.AreEqual(11, f.Length);
|
||||
ClassicAssert.AreEqual(10, f.Index);
|
||||
ClassicAssert.AreEqual(11, f.Length);
|
||||
|
||||
f = result.Links.Find(l => l.Url == "http://google.com");
|
||||
Assert.That(f, Is.Not.Null);
|
||||
Assert.AreEqual(97, f.Index);
|
||||
Assert.AreEqual(4, f.Length);
|
||||
ClassicAssert.AreEqual(97, f.Index);
|
||||
ClassicAssert.AreEqual(4, f.Length);
|
||||
|
||||
f = result.Links.Find(l => l.Url == "https://osu.ppy.sh");
|
||||
Assert.That(f, Is.Not.Null);
|
||||
Assert.AreEqual(78, f.Index);
|
||||
Assert.AreEqual(18, f.Length);
|
||||
ClassicAssert.AreEqual(78, f.Index);
|
||||
ClassicAssert.AreEqual(18, f.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmoji()
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "Hello world\uD83D\uDE12<--This is an emoji,There are more emojis among us:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20" });
|
||||
Assert.AreEqual("Hello world[emoji]<--This is an emoji,There are more emojis among us:[emoji][emoji],[emoji]", result.DisplayContent);
|
||||
Assert.AreEqual(result.Links.Count, 0);
|
||||
ClassicAssert.AreEqual("Hello world[emoji]<--This is an emoji,There are more emojis among us:[emoji][emoji],[emoji]", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(result.Links.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEmojiWithSuccessiveParens()
|
||||
{
|
||||
Message result = MessageFormatter.FormatMessage(new Message { Content = "\uD83D\uDE10(let's hope this doesn't accidentally turn into a link)" });
|
||||
Assert.AreEqual("[emoji](let's hope this doesn't accidentally turn into a link)", result.DisplayContent);
|
||||
Assert.AreEqual(result.Links.Count, 0);
|
||||
ClassicAssert.AreEqual("[emoji](let's hope this doesn't accidentally turn into a link)", result.DisplayContent);
|
||||
ClassicAssert.AreEqual(result.Links.Count, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -541,8 +542,8 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
LinkDetails result = MessageFormatter.GetLinkDetails("https://google.com");
|
||||
|
||||
Assert.AreEqual(LinkAction.External, result.Action);
|
||||
Assert.AreEqual("https://google.com", result.Argument);
|
||||
ClassicAssert.AreEqual(LinkAction.External, result.Action);
|
||||
ClassicAssert.AreEqual("https://google.com", result.Argument);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -550,8 +551,8 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
LinkDetails result = MessageFormatter.GetLinkDetails("/relative");
|
||||
|
||||
Assert.AreEqual(LinkAction.External, result.Action);
|
||||
Assert.AreEqual("/relative", result.Argument);
|
||||
ClassicAssert.AreEqual(LinkAction.External, result.Action);
|
||||
ClassicAssert.AreEqual("/relative", result.Argument);
|
||||
}
|
||||
|
||||
[TestCase("https://dev.ppy.sh/home/changelog", "")]
|
||||
@@ -560,8 +561,8 @@ namespace osu.Game.Tests.Chat
|
||||
{
|
||||
LinkDetails result = MessageFormatter.GetLinkDetails(link);
|
||||
|
||||
Assert.AreEqual(LinkAction.OpenChangelog, result.Action);
|
||||
Assert.AreEqual(expectedArg, result.Argument);
|
||||
ClassicAssert.AreEqual(LinkAction.OpenChangelog, result.Action);
|
||||
ClassicAssert.AreEqual(expectedArg, result.Argument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Logging;
|
||||
@@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
|
||||
Assert.NotNull(beatmapSet);
|
||||
ClassicAssert.NotNull(beatmapSet);
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
BeatmapSetInfo? detachedBeatmapSet = null;
|
||||
@@ -52,23 +53,23 @@ namespace osu.Game.Tests.Database
|
||||
detachedBeatmapSet = live.Detach();
|
||||
|
||||
// files are omitted
|
||||
Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
|
||||
ClassicAssert.AreEqual(0, detachedBeatmapSet.Files.Count);
|
||||
|
||||
Assert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
|
||||
Assert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
Assert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata);
|
||||
ClassicAssert.AreEqual(live.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
|
||||
ClassicAssert.AreEqual(live.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
ClassicAssert.AreEqual(live.Metadata, detachedBeatmapSet.Metadata);
|
||||
});
|
||||
|
||||
Debug.Assert(detachedBeatmapSet != null);
|
||||
|
||||
// Check detached instances can all be accessed without throwing.
|
||||
Assert.AreEqual(0, detachedBeatmapSet.Files.Count);
|
||||
Assert.NotNull(detachedBeatmapSet.Beatmaps.Count);
|
||||
Assert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
Assert.NotNull(detachedBeatmapSet.Metadata);
|
||||
ClassicAssert.AreEqual(0, detachedBeatmapSet.Files.Count);
|
||||
ClassicAssert.NotNull(detachedBeatmapSet.Beatmaps.Count);
|
||||
ClassicAssert.NotZero(detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
ClassicAssert.NotNull(detachedBeatmapSet.Metadata);
|
||||
|
||||
// Check cyclic reference to beatmap set
|
||||
Assert.AreEqual(detachedBeatmapSet, detachedBeatmapSet.Beatmaps.First().BeatmapSet);
|
||||
ClassicAssert.AreEqual(detachedBeatmapSet, detachedBeatmapSet.Beatmaps.First().BeatmapSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -84,7 +85,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
|
||||
Assert.NotNull(beatmapSet);
|
||||
ClassicAssert.NotNull(beatmapSet);
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
// Detach at the BeatmapInfo point, similar to what GetWorkingBeatmap does.
|
||||
@@ -101,7 +102,7 @@ namespace osu.Game.Tests.Database
|
||||
detachedBeatmapSet.Beatmaps.First().Metadata.Artist = "New Artist";
|
||||
detachedBeatmapSet.Beatmaps.First().Metadata.Author = newUser;
|
||||
|
||||
Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
|
||||
ClassicAssert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
|
||||
detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
|
||||
|
||||
beatmapSet.PerformWrite(detachedBeatmapSet.CopyChangesToRealm);
|
||||
@@ -109,17 +110,17 @@ namespace osu.Game.Tests.Database
|
||||
beatmapSet.PerformRead(s =>
|
||||
{
|
||||
// Check above changes explicitly.
|
||||
Assert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status);
|
||||
Assert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist);
|
||||
Assert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author);
|
||||
Assert.NotZero(s.Files.Count);
|
||||
ClassicAssert.AreEqual(BeatmapOnlineStatus.Ranked, s.Status);
|
||||
ClassicAssert.AreEqual("New Artist", s.Beatmaps.First().Metadata.Artist);
|
||||
ClassicAssert.AreEqual(newUser, s.Beatmaps.First().Metadata.Author);
|
||||
ClassicAssert.NotZero(s.Files.Count);
|
||||
|
||||
// Check nothing was lost in the copy operation.
|
||||
Assert.AreEqual(s.Files.Count, detachedBeatmapSet.Files.Count);
|
||||
Assert.AreEqual(s.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count());
|
||||
Assert.AreEqual(s.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
|
||||
Assert.AreEqual(s.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
Assert.AreEqual(s.Metadata, detachedBeatmapSet.Metadata);
|
||||
ClassicAssert.AreEqual(s.Files.Count, detachedBeatmapSet.Files.Count);
|
||||
ClassicAssert.AreEqual(s.Files.Select(f => f.File).Count(), detachedBeatmapSet.Files.Select(f => f.File).Count());
|
||||
ClassicAssert.AreEqual(s.Beatmaps.Count, detachedBeatmapSet.Beatmaps.Count);
|
||||
ClassicAssert.AreEqual(s.Beatmaps.Select(f => f.Difficulty).Count(), detachedBeatmapSet.Beatmaps.Select(f => f.Difficulty).Count());
|
||||
ClassicAssert.AreEqual(s.Metadata, detachedBeatmapSet.Metadata);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -142,7 +143,7 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
|
||||
Assert.NotNull(beatmapSet);
|
||||
ClassicAssert.NotNull(beatmapSet);
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
// Intentionally detach on async thread as to not trigger a refresh on the main thread.
|
||||
@@ -167,20 +168,20 @@ namespace osu.Game.Tests.Database
|
||||
var imported = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz"));
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
||||
ClassicAssert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count());
|
||||
|
||||
Assert.NotNull(imported);
|
||||
ClassicAssert.NotNull(imported);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
imported.PerformWrite(s => s.DeletePending = true);
|
||||
|
||||
Assert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
||||
ClassicAssert.AreEqual(1, realm.Realm.All<BeatmapSetInfo>().Count(s => s.DeletePending));
|
||||
}
|
||||
});
|
||||
|
||||
Logger.Log("Running with no work to purge pending deletions");
|
||||
|
||||
RunTestWithRealm((realm, _) => { Assert.AreEqual(0, realm.Realm.All<BeatmapSetInfo>().Count()); });
|
||||
RunTestWithRealm((realm, _) => { ClassicAssert.AreEqual(0, realm.Realm.All<BeatmapSetInfo>().Count()); });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -208,8 +209,8 @@ namespace osu.Game.Tests.Database
|
||||
var beatmap = imported.Beatmaps.First();
|
||||
var file = beatmap.File;
|
||||
|
||||
Assert.NotNull(file);
|
||||
Assert.AreEqual(beatmap.Hash, file!.File.Hash);
|
||||
ClassicAssert.NotNull(file);
|
||||
ClassicAssert.AreEqual(beatmap.Hash, file!.File.Hash);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -245,10 +246,10 @@ namespace osu.Game.Tests.Database
|
||||
EnsureLoaded(realm.Realm);
|
||||
}
|
||||
|
||||
Assert.NotNull(importedSet);
|
||||
ClassicAssert.NotNull(importedSet);
|
||||
Debug.Assert(importedSet != null);
|
||||
|
||||
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
|
||||
ClassicAssert.True(File.Exists(tempPath), "Stream source file somehow went missing");
|
||||
File.Delete(tempPath);
|
||||
|
||||
var imported = realm.Realm.All<BeatmapSetInfo>().First(beatmapSet => beatmapSet.ID == importedSet.ID);
|
||||
@@ -269,8 +270,8 @@ namespace osu.Game.Tests.Database
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
|
||||
checkBeatmapSetCount(realm.Realm, 1);
|
||||
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||
@@ -292,7 +293,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
foreach (var file in new DirectoryInfo(extractedFolder).GetFiles("*.osu"))
|
||||
@@ -304,7 +305,7 @@ namespace osu.Game.Tests.Database
|
||||
}
|
||||
|
||||
var imported = await importer.Import(new ImportTask(extractedFolder));
|
||||
Assert.IsNull(imported);
|
||||
ClassicAssert.Null(imported);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -333,28 +334,28 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
string hashBefore = hashFile(temp);
|
||||
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
}
|
||||
|
||||
// zip files differ because different compression or encoder.
|
||||
Assert.AreNotEqual(hashBefore, hashFile(temp));
|
||||
ClassicAssert.AreNotEqual(hashBefore, hashFile(temp));
|
||||
|
||||
var importedSecondTime = await importer.Import(new ImportTask(temp));
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.NotNull(importedSecondTime);
|
||||
ClassicAssert.NotNull(importedSecondTime);
|
||||
Debug.Assert(importedSecondTime != null);
|
||||
|
||||
// but contents doesn't, so existing should still be used.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -382,7 +383,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
await createScoreForBeatmap(realm.Realm, imported.Beatmaps.First());
|
||||
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
// arbitrary write to hashed file
|
||||
@@ -390,7 +391,7 @@ namespace osu.Game.Tests.Database
|
||||
using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).AppendText())
|
||||
await sw.WriteLineAsync("// changed");
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -401,11 +402,11 @@ namespace osu.Game.Tests.Database
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is not the original.
|
||||
Assert.NotNull(importedSecondTime);
|
||||
ClassicAssert.NotNull(importedSecondTime);
|
||||
Debug.Assert(importedSecondTime != null);
|
||||
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
ClassicAssert.True(imported.ID != importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -501,7 +502,7 @@ namespace osu.Game.Tests.Database
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is not the original.
|
||||
Assert.NotNull(importedSecondTime);
|
||||
ClassicAssert.NotNull(importedSecondTime);
|
||||
Debug.Assert(importedSecondTime != null);
|
||||
Assert.That(imported.ID != importedSecondTime.ID);
|
||||
|
||||
@@ -533,14 +534,14 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
// arbitrary write to non-hashed file
|
||||
using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText())
|
||||
await sw.WriteLineAsync("text");
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -550,12 +551,12 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.NotNull(importedSecondTime);
|
||||
ClassicAssert.NotNull(importedSecondTime);
|
||||
Debug.Assert(importedSecondTime != null);
|
||||
|
||||
// check the newly "imported" beatmap is not the original.
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
ClassicAssert.True(imported.ID != importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -581,14 +582,14 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
// change filename
|
||||
var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First());
|
||||
firstFile.MoveTo(Path.Combine(firstFile.DirectoryName.AsNonNull(), $"{firstFile.Name}-changed{firstFile.Extension}"));
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -598,12 +599,12 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.NotNull(importedSecondTime);
|
||||
ClassicAssert.NotNull(importedSecondTime);
|
||||
Debug.Assert(importedSecondTime != null);
|
||||
|
||||
// check the newly "imported" beatmap is not the original.
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
ClassicAssert.True(imported.ID != importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID != importedSecondTime.PerformRead(s => s.Beatmaps.First().ID));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -636,11 +637,11 @@ namespace osu.Game.Tests.Database
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
using (var stream = fileStorage.GetStream(firstFile.File.GetStoragePath()))
|
||||
Assert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
|
||||
ClassicAssert.AreEqual(stream.Length, originalLength, "Corruption was not fixed on second import");
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
|
||||
checkBeatmapSetCount(realm.Realm, 1);
|
||||
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||
@@ -659,7 +660,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var zipStream = new MemoryStream();
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
|
||||
|
||||
var imported = await importer.Import(
|
||||
@@ -672,8 +673,8 @@ namespace osu.Game.Tests.Database
|
||||
checkBeatmapSetCount(realm.Realm, 0);
|
||||
checkBeatmapCount(realm.Realm, 0);
|
||||
|
||||
Assert.IsEmpty(imported);
|
||||
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
||||
ClassicAssert.IsEmpty(imported);
|
||||
ClassicAssert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -709,7 +710,7 @@ namespace osu.Game.Tests.Database
|
||||
File.Delete(brokenTempFilename);
|
||||
|
||||
using (var outStream = File.Open(brokenTempFilename, FileMode.CreateNew))
|
||||
using (var zip = ZipArchive.Open(brokenOsz))
|
||||
using (var zip = ZipArchive.OpenArchive(brokenOsz))
|
||||
{
|
||||
foreach (var entry in zip.Entries.ToArray())
|
||||
{
|
||||
@@ -737,7 +738,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
checkSingleReferencedFileCount(realm.Realm, 18);
|
||||
|
||||
Assert.AreEqual(0, loggedExceptionCount);
|
||||
ClassicAssert.AreEqual(0, loggedExceptionCount);
|
||||
|
||||
File.Delete(brokenTempFilename);
|
||||
});
|
||||
@@ -755,17 +756,17 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
deleteBeatmapSet(imported, realm.Realm);
|
||||
|
||||
Assert.IsTrue(imported.DeletePending);
|
||||
ClassicAssert.True(imported.DeletePending);
|
||||
|
||||
var originalAddedDate = imported.DateAdded;
|
||||
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
Assert.IsFalse(imported.DeletePending);
|
||||
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.False(imported.DeletePending);
|
||||
ClassicAssert.False(importedSecondTime.DeletePending);
|
||||
Assert.That(importedSecondTime.DateAdded, Is.GreaterThan(originalAddedDate));
|
||||
});
|
||||
}
|
||||
@@ -787,13 +788,13 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(pathOriginal))
|
||||
using (var zip = ZipArchive.OpenArchive(pathOriginal))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
// remove one difficulty before first import
|
||||
new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete();
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -841,7 +842,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
deleteBeatmapSet(imported, realmFactory.Realm);
|
||||
|
||||
Assert.IsTrue(imported.DeletePending);
|
||||
ClassicAssert.True(imported.DeletePending);
|
||||
|
||||
// intentionally nuke all files
|
||||
storage.DeleteDirectory("files");
|
||||
@@ -851,10 +852,10 @@ namespace osu.Game.Tests.Database
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
Assert.IsFalse(imported.DeletePending);
|
||||
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.False(imported.DeletePending);
|
||||
ClassicAssert.False(importedSecondTime.DeletePending);
|
||||
|
||||
// check that the files now exist, even though they were deleted above.
|
||||
Assert.That(importedSecondTime.Files.All(f => storage.GetStorageForDirectory("files").Exists(f.File.GetStoragePath())));
|
||||
@@ -873,17 +874,17 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
deleteBeatmapSet(imported, realm.Realm);
|
||||
|
||||
Assert.IsTrue(imported.DeletePending);
|
||||
ClassicAssert.True(imported.DeletePending);
|
||||
|
||||
var originalAddedDate = imported.DateAdded;
|
||||
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID == importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
Assert.IsFalse(imported.DeletePending);
|
||||
Assert.IsFalse(importedSecondTime.DeletePending);
|
||||
ClassicAssert.True(imported.ID == importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.False(imported.DeletePending);
|
||||
ClassicAssert.False(importedSecondTime.DeletePending);
|
||||
Assert.That(importedSecondTime.DateAdded, Is.GreaterThan(originalAddedDate));
|
||||
});
|
||||
}
|
||||
@@ -909,8 +910,8 @@ namespace osu.Game.Tests.Database
|
||||
var importedSecondTime = await LoadOszIntoStore(importer, realm.Realm);
|
||||
|
||||
// check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched)
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
|
||||
ClassicAssert.True(imported.ID != importedSecondTime.ID);
|
||||
ClassicAssert.True(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -954,11 +955,11 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
realm.Run(r => r.Refresh());
|
||||
|
||||
Assert.NotNull(imported);
|
||||
ClassicAssert.NotNull(imported);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
Assert.AreEqual(-1, imported.PerformRead(s => s.Beatmaps[0].OnlineID));
|
||||
Assert.AreEqual(-1, imported.PerformRead(s => s.Beatmaps[1].OnlineID));
|
||||
ClassicAssert.AreEqual(-1, imported.PerformRead(s => s.Beatmaps[0].OnlineID));
|
||||
ClassicAssert.AreEqual(-1, imported.PerformRead(s => s.Beatmaps[1].OnlineID));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -975,7 +976,7 @@ namespace osu.Game.Tests.Database
|
||||
await importer.Import(temp);
|
||||
EnsureLoaded(realm.Realm);
|
||||
File.Delete(temp);
|
||||
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
||||
ClassicAssert.False(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -994,10 +995,10 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.AddEntry("duplicate.osu", Directory.GetFiles(extractedFolder, "*.osu").First());
|
||||
@@ -1030,7 +1031,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
var subdirectory = Directory.CreateDirectory(Path.Combine(extractedFolder, "subdir"));
|
||||
@@ -1041,7 +1042,7 @@ namespace osu.Game.Tests.Database
|
||||
using (var textWriter = new StreamWriter(stream))
|
||||
await textWriter.WriteLineAsync("# adding a comment so that the hashes are different");
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -1075,10 +1076,10 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(subfolder);
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -1086,12 +1087,12 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var imported = await importer.Import(new ImportTask(temp));
|
||||
|
||||
Assert.NotNull(imported);
|
||||
ClassicAssert.NotNull(imported);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
|
||||
ClassicAssert.False(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("subfolder"))), "Files contain common subfolder");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1125,10 +1126,10 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
try
|
||||
{
|
||||
using (var zip = ZipArchive.Open(temp))
|
||||
using (var zip = ZipArchive.OpenArchive(temp))
|
||||
zip.WriteToDirectory(dataFolder);
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
|
||||
@@ -1136,13 +1137,13 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var imported = await importer.Import(new ImportTask(temp));
|
||||
|
||||
Assert.NotNull(imported);
|
||||
ClassicAssert.NotNull(imported);
|
||||
Debug.Assert(imported != null);
|
||||
|
||||
EnsureLoaded(realm.Realm);
|
||||
|
||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
|
||||
Assert.IsFalse(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
|
||||
ClassicAssert.False(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("__MACOSX"))), "Files contain resource fork folder, which should be ignored");
|
||||
ClassicAssert.False(imported.PerformRead(s => s.Files.Any(f => f.Filename.Contains("actual_data"))), "Files contain common subfolder");
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1182,7 +1183,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var importedSet = await importer.Import(new ImportTask(temp));
|
||||
|
||||
Assert.NotNull(importedSet);
|
||||
ClassicAssert.NotNull(importedSet);
|
||||
|
||||
EnsureLoaded(realm);
|
||||
|
||||
@@ -1197,7 +1198,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var importedSet = await importer.Import(new ImportTask(temp), new ImportParameters { Batch = batchImport });
|
||||
|
||||
Assert.NotNull(importedSet);
|
||||
ClassicAssert.NotNull(importedSet);
|
||||
Debug.Assert(importedSet != null);
|
||||
|
||||
EnsureLoaded(realm);
|
||||
@@ -1214,7 +1215,7 @@ namespace osu.Game.Tests.Database
|
||||
checkBeatmapSetCount(realm, 0);
|
||||
checkBeatmapSetCount(realm, 1, true);
|
||||
|
||||
Assert.IsTrue(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
||||
ClassicAssert.True(realm.All<BeatmapSetInfo>().First(_ => true).DeletePending);
|
||||
}
|
||||
|
||||
private static Task createScoreForBeatmap(Realm realm, BeatmapInfo beatmap) =>
|
||||
@@ -1230,7 +1231,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
private static void checkBeatmapSetCount(Realm realm, int expected, bool includeDeletePending = false)
|
||||
{
|
||||
Assert.AreEqual(expected, includeDeletePending
|
||||
ClassicAssert.AreEqual(expected, includeDeletePending
|
||||
? realm.All<BeatmapSetInfo>().Count()
|
||||
: realm.All<BeatmapSetInfo>().Count(s => !s.DeletePending));
|
||||
}
|
||||
@@ -1243,7 +1244,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
private static void checkBeatmapCount(Realm realm, int expected)
|
||||
{
|
||||
Assert.AreEqual(expected, realm.All<BeatmapInfo>().Where(_ => true).ToList().Count);
|
||||
ClassicAssert.AreEqual(expected, realm.All<BeatmapInfo>().Where(_ => true).ToList().Count);
|
||||
}
|
||||
|
||||
private static void checkSingleReferencedFileCount(Realm realm, int expected)
|
||||
@@ -1256,7 +1257,7 @@ namespace osu.Game.Tests.Database
|
||||
singleReferencedCount++;
|
||||
}
|
||||
|
||||
Assert.AreEqual(expected, singleReferencedCount);
|
||||
ClassicAssert.AreEqual(expected, singleReferencedCount);
|
||||
}
|
||||
|
||||
internal static void EnsureLoaded(Realm realm, int timeout = 60000)
|
||||
@@ -1270,7 +1271,7 @@ namespace osu.Game.Tests.Database
|
||||
}, @"BeatmapSet did not import to the database in allocated time.", timeout);
|
||||
|
||||
// ensure we were stored to beatmap database backing...
|
||||
Assert.IsTrue(resultSets?.Count() == 1, $@"Incorrect result count found ({resultSets?.Count()} but should be 1).");
|
||||
ClassicAssert.True(resultSets?.Count() == 1, $@"Incorrect result count found ({resultSets?.Count()} but should be 1).");
|
||||
|
||||
IEnumerable<BeatmapSetInfo> queryBeatmapSets() => realm.All<BeatmapSetInfo>().Where(s => !s.DeletePending && s.OnlineID == 241526);
|
||||
|
||||
@@ -1279,20 +1280,20 @@ namespace osu.Game.Tests.Database
|
||||
// ReSharper disable once PossibleUnintendedReferenceComparison
|
||||
IEnumerable<BeatmapInfo> queryBeatmaps() => realm.All<BeatmapInfo>().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
|
||||
|
||||
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
|
||||
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
|
||||
ClassicAssert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
|
||||
ClassicAssert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
|
||||
|
||||
int countBeatmapSetBeatmaps;
|
||||
int countBeatmaps;
|
||||
|
||||
Assert.AreEqual(
|
||||
ClassicAssert.AreEqual(
|
||||
countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count,
|
||||
countBeatmaps = queryBeatmaps().Count(),
|
||||
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
|
||||
|
||||
foreach (BeatmapInfo b in set.Beatmaps)
|
||||
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||
Assert.IsTrue(set.Beatmaps.Count > 0);
|
||||
ClassicAssert.True(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
|
||||
ClassicAssert.True(set.Beatmaps.Count > 0);
|
||||
}
|
||||
|
||||
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
|
||||
|
||||
@@ -680,14 +680,14 @@ namespace osu.Game.Tests.Database
|
||||
string extractedFolder = $"{path}_extracted";
|
||||
Directory.CreateDirectory(extractedFolder);
|
||||
|
||||
using (var zip = ZipArchive.Open(path))
|
||||
using (var zip = ZipArchive.OpenArchive(path))
|
||||
zip.WriteToDirectory(extractedFolder);
|
||||
|
||||
applyModifications(new DirectoryInfo(extractedFolder));
|
||||
|
||||
File.Delete(path);
|
||||
|
||||
using (var zip = ZipArchive.Create())
|
||||
using (var zip = ZipArchive.CreateArchive())
|
||||
{
|
||||
zip.AddAllFromDirectory(extractedFolder);
|
||||
zip.SaveTo(path, new ZipWriterOptions(CompressionType.Deflate));
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
@@ -26,8 +27,8 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
realm.Write(() => files.Add(testData, realm));
|
||||
|
||||
Assert.True(files.Storage.Exists("0/05/054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8"));
|
||||
Assert.True(files.Storage.Exists(realm.All<RealmFile>().First().GetStoragePath()));
|
||||
ClassicAssert.True(files.Storage.Exists("0/05/054edec1d0211f624fed0cbca9d4f9400b0e491c43742af2c5b0abebf0c990d8"));
|
||||
ClassicAssert.True(files.Storage.Exists(realm.All<RealmFile>().First().GetStoragePath()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ namespace osu.Game.Tests.Database
|
||||
realm.Write(() => files.Add(testData, realm));
|
||||
realm.Write(() => files.Add(testData, realm));
|
||||
|
||||
Assert.AreEqual(1, realm.All<RealmFile>().Count());
|
||||
ClassicAssert.AreEqual(1, realm.All<RealmFile>().Count());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,15 +76,15 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
string path = file.GetStoragePath();
|
||||
|
||||
Assert.True(realm.All<RealmFile>().Any());
|
||||
Assert.True(files.Storage.Exists(path));
|
||||
ClassicAssert.True(realm.All<RealmFile>().Any());
|
||||
ClassicAssert.True(files.Storage.Exists(path));
|
||||
|
||||
files.Cleanup();
|
||||
Logger.Log($"Cleanup complete at {timer.ElapsedMilliseconds}");
|
||||
|
||||
Assert.True(realm.All<RealmFile>().Any());
|
||||
Assert.True(file.IsValid);
|
||||
Assert.True(files.Storage.Exists(path));
|
||||
ClassicAssert.True(realm.All<RealmFile>().Any());
|
||||
ClassicAssert.True(file.IsValid);
|
||||
ClassicAssert.True(files.Storage.Exists(path));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,14 +100,14 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
string path = file.GetStoragePath();
|
||||
|
||||
Assert.True(realm.All<RealmFile>().Any());
|
||||
Assert.True(files.Storage.Exists(path));
|
||||
ClassicAssert.True(realm.All<RealmFile>().Any());
|
||||
ClassicAssert.True(files.Storage.Exists(path));
|
||||
|
||||
files.Cleanup();
|
||||
|
||||
Assert.False(realm.All<RealmFile>().Any());
|
||||
Assert.False(file.IsValid);
|
||||
Assert.False(files.Storage.Exists(path));
|
||||
ClassicAssert.False(realm.All<RealmFile>().Any());
|
||||
ClassicAssert.False(file.IsValid);
|
||||
ClassicAssert.False(files.Storage.Exists(path));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
@@ -143,7 +144,7 @@ namespace osu.Game.Tests.Database
|
||||
return null;
|
||||
});
|
||||
|
||||
Assert.IsTrue(callbackRan);
|
||||
ClassicAssert.True(callbackRan);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -93,8 +94,8 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
var importedSet = realm.Realm.All<BeatmapSetInfo>().Single();
|
||||
|
||||
Assert.NotNull(importedSet);
|
||||
Assert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1, 12, 0, 0, DateTimeKind.Utc)), importedSet.DateAdded);
|
||||
ClassicAssert.NotNull(importedSet);
|
||||
ClassicAssert.AreEqual(new DateTimeOffset(new DateTime(2000, 1, 1, 12, 0, 0, DateTimeKind.Utc)), importedSet.DateAdded);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -25,7 +26,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
Live<BeatmapInfo> beatmap2 = realm.Run(r => r.All<BeatmapInfo>().First().ToLive(realm));
|
||||
|
||||
Assert.AreEqual(beatmap, beatmap2);
|
||||
ClassicAssert.AreEqual(beatmap, beatmap2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ namespace osu.Game.Tests.Database
|
||||
using (realm.BlockAllOperations("testing"))
|
||||
storage.Migrate(migratedStorage);
|
||||
|
||||
Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(liveBeatmap?.PerformRead(l => l.Hidden));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -111,7 +112,7 @@ namespace osu.Game.Tests.Database
|
||||
r.Add(beatmap)))
|
||||
);
|
||||
|
||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -126,7 +127,7 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
realm.Run(r => r.Write(_ => r.Add(beatmap)));
|
||||
|
||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,15 +137,15 @@ namespace osu.Game.Tests.Database
|
||||
var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata());
|
||||
var liveBeatmap = beatmap.ToLiveUnmanaged();
|
||||
|
||||
Assert.IsFalse(beatmap.Hidden);
|
||||
Assert.IsFalse(liveBeatmap.Value.Hidden);
|
||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(beatmap.Hidden);
|
||||
ClassicAssert.False(liveBeatmap.Value.Hidden);
|
||||
ClassicAssert.False(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => l.Hidden = true));
|
||||
|
||||
Assert.IsFalse(beatmap.Hidden);
|
||||
Assert.IsFalse(liveBeatmap.Value.Hidden);
|
||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(beatmap.Hidden);
|
||||
ClassicAssert.False(liveBeatmap.Value.Hidden);
|
||||
ClassicAssert.False(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -159,10 +160,10 @@ namespace osu.Game.Tests.Database
|
||||
var liveBeatmap = beatmap.ToLive(realm);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => liveBeatmap.PerformWrite(l => throw new InvalidOperationException()));
|
||||
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.False(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
|
||||
liveBeatmap.PerformWrite(l => l.Hidden = true);
|
||||
Assert.IsTrue(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
ClassicAssert.True(liveBeatmap.PerformRead(l => l.Hidden));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,8 +189,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
liveBeatmap.PerformRead(beatmap =>
|
||||
{
|
||||
Assert.IsTrue(beatmap.IsValid);
|
||||
Assert.IsFalse(beatmap.Hidden);
|
||||
ClassicAssert.True(beatmap.IsValid);
|
||||
ClassicAssert.False(beatmap.Hidden);
|
||||
});
|
||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||
});
|
||||
@@ -216,7 +217,7 @@ namespace osu.Game.Tests.Database
|
||||
Task.Factory.StartNew(() =>
|
||||
{
|
||||
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
|
||||
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
|
||||
liveBeatmap.PerformRead(beatmap => { ClassicAssert.True(beatmap.Hidden); });
|
||||
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
|
||||
});
|
||||
}
|
||||
@@ -333,17 +334,17 @@ namespace osu.Game.Tests.Database
|
||||
Debug.Assert(liveBeatmap != null);
|
||||
|
||||
// not yet seen by main context
|
||||
Assert.AreEqual(0, outerRealm.All<BeatmapInfo>().Count());
|
||||
Assert.AreEqual(0, changesTriggered);
|
||||
ClassicAssert.AreEqual(0, outerRealm.All<BeatmapInfo>().Count());
|
||||
ClassicAssert.AreEqual(0, changesTriggered);
|
||||
|
||||
liveBeatmap.PerformRead(resolved =>
|
||||
{
|
||||
// retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point.
|
||||
Assert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
||||
Assert.AreEqual(1, changesTriggered);
|
||||
ClassicAssert.AreEqual(2, outerRealm.All<BeatmapInfo>().Count());
|
||||
ClassicAssert.AreEqual(1, changesTriggered);
|
||||
|
||||
// can access properties without a crash.
|
||||
Assert.IsFalse(resolved.Hidden);
|
||||
ClassicAssert.False(resolved.Hidden);
|
||||
|
||||
outerRealm.Write(r =>
|
||||
{
|
||||
|
||||
@@ -5,8 +5,10 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -26,8 +28,8 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
ClassicAssert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
ClassicAssert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,11 +41,11 @@ namespace osu.Game.Tests.Database
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
using var rulesets2 = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||
ClassicAssert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||
ClassicAssert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||
|
||||
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||
Assert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
ClassicAssert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||
ClassicAssert.AreEqual(4, realm.Realm.All<RulesetInfo>().Count());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -54,9 +56,9 @@ namespace osu.Game.Tests.Database
|
||||
{
|
||||
using var rulesets = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.IsFalse(rulesets.AvailableRulesets.First().IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset(0)?.IsManaged);
|
||||
Assert.IsFalse(rulesets.GetRuleset("mania")?.IsManaged);
|
||||
ClassicAssert.False(rulesets.AvailableRulesets.First().IsManaged);
|
||||
ClassicAssert.False(rulesets.GetRuleset(0)?.IsManaged);
|
||||
ClassicAssert.False(rulesets.GetRuleset("mania")?.IsManaged);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -116,6 +118,69 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFakedRulesetIdIsDetected()
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
LoadTestRuleset.HasImplementations = true;
|
||||
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
|
||||
|
||||
var ruleset = new LoadTestRuleset();
|
||||
string rulesetShortName = ruleset.RulesetInfo.ShortName;
|
||||
|
||||
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, 0)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleRulesetWithSameOnlineIdsAreDetected()
|
||||
{
|
||||
RunTestWithRealm((realm, storage) =>
|
||||
{
|
||||
LoadTestRuleset.HasImplementations = true;
|
||||
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
|
||||
LoadTestRuleset.OnlineID = 2;
|
||||
|
||||
var first = new LoadTestRuleset();
|
||||
var second = new CatchRuleset();
|
||||
|
||||
realm.Write(r => r.Add(new RulesetInfo(first.ShortName, first.RulesetInfo.Name, first.RulesetInfo.InstantiationInfo, first.RulesetInfo.OnlineID)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
realm.Write(r => r.Add(new RulesetInfo(second.ShortName, second.RulesetInfo.Name, second.RulesetInfo.InstantiationInfo, second.RulesetInfo.OnlineID)
|
||||
{
|
||||
Available = true,
|
||||
}));
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.True);
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
|
||||
|
||||
// Availability is updated on construction of a RealmRulesetStore
|
||||
using var _ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.False);
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.False);
|
||||
|
||||
realm.Write(r => r.Remove(r.Find<RulesetInfo>(first.ShortName)!));
|
||||
|
||||
using var __ = new RealmRulesetStore(realm, storage);
|
||||
|
||||
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
|
||||
});
|
||||
}
|
||||
|
||||
private class LoadTestRuleset : Ruleset
|
||||
{
|
||||
public override string RulesetAPIVersionSupported => Version;
|
||||
@@ -124,6 +189,13 @@ namespace osu.Game.Tests.Database
|
||||
|
||||
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
|
||||
|
||||
public static int OnlineID { get; set; } = -1;
|
||||
|
||||
public LoadTestRuleset()
|
||||
{
|
||||
RulesetInfo.OnlineID = OnlineID;
|
||||
}
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type)
|
||||
{
|
||||
if (!HasImplementations)
|
||||
|
||||
@@ -3,14 +3,11 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Input.Bindings;
|
||||
@@ -20,63 +17,87 @@ using Realms;
|
||||
namespace osu.Game.Tests.Database
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestRealmKeyBindingStore
|
||||
public partial class TestRealmKeyBindingStore : RealmTest
|
||||
{
|
||||
private NativeStorage storage;
|
||||
|
||||
private RealmKeyBindingStore keyBindingStore;
|
||||
|
||||
private RealmAccess realm;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));
|
||||
|
||||
storage = new NativeStorage(directory.FullName);
|
||||
|
||||
realm = new RealmAccess(storage, "test");
|
||||
keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultsPopulationAndQuery()
|
||||
{
|
||||
Assert.That(queryCount(), Is.EqualTo(0));
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
Assert.That(queryCount(realm), Is.EqualTo(0));
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
var keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(), Is.EqualTo(3));
|
||||
Assert.That(queryCount(realm), Is.EqualTo(3));
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
|
||||
Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2));
|
||||
Assert.That(queryCount(realm, GlobalAction.Back), Is.EqualTo(1));
|
||||
Assert.That(queryCount(realm, GlobalAction.Select), Is.EqualTo(2));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultsPopulationRemovesExcess()
|
||||
{
|
||||
Assert.That(queryCount(), Is.EqualTo(0));
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
// Add some excess bindings for an action which only supports 1.
|
||||
realm.Write(r =>
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
||||
Assert.That(queryCount(realm), Is.EqualTo(0));
|
||||
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
// Add some excess bindings for an action which only supports 1.
|
||||
realm.Write(r =>
|
||||
{
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.A)));
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.S)));
|
||||
r.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.D)));
|
||||
});
|
||||
|
||||
Assert.That(queryCount(realm, GlobalAction.Back), Is.EqualTo(3));
|
||||
|
||||
var keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(realm, GlobalAction.Back), Is.EqualTo(1));
|
||||
});
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3));
|
||||
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
private int queryCount(GlobalAction? match = null)
|
||||
[Test]
|
||||
public void TestUpdateViaQueriedReference()
|
||||
{
|
||||
RunTestWithRealm((realm, _) =>
|
||||
{
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
var keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
realm.Run(outerRealm =>
|
||||
{
|
||||
var backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
|
||||
|
||||
var tsr = ThreadSafeReference.Create(backBinding);
|
||||
|
||||
realm.Run(innerRealm =>
|
||||
{
|
||||
var binding = innerRealm.ResolveReference(tsr)!;
|
||||
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
||||
});
|
||||
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||
|
||||
// check still correct after re-query.
|
||||
backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static int queryCount(RealmAccess realm, GlobalAction? match = null)
|
||||
{
|
||||
return realm.Run(r =>
|
||||
{
|
||||
@@ -87,42 +108,6 @@ namespace osu.Game.Tests.Database
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUpdateViaQueriedReference()
|
||||
{
|
||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||
|
||||
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||
|
||||
realm.Run(outerRealm =>
|
||||
{
|
||||
var backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.Escape }));
|
||||
|
||||
var tsr = ThreadSafeReference.Create(backBinding);
|
||||
|
||||
realm.Run(innerRealm =>
|
||||
{
|
||||
var binding = innerRealm.ResolveReference(tsr)!;
|
||||
innerRealm.Write(() => binding.KeyCombination = new KeyCombination(InputKey.BackSpace));
|
||||
});
|
||||
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||
|
||||
// check still correct after re-query.
|
||||
backBinding = outerRealm.All<RealmKeyBinding>().Single(k => k.ActionInt == (int)GlobalAction.Back);
|
||||
Assert.That(backBinding.KeyCombination.Keys, Is.EquivalentTo(new[] { InputKey.BackSpace }));
|
||||
});
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
realm.Dispose();
|
||||
storage.DeleteDirectory(string.Empty);
|
||||
}
|
||||
|
||||
public partial class TestKeyBindingContainer : KeyBindingContainer
|
||||
{
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings =>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
@@ -44,7 +45,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
public void TestRegularVideoFile()
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Videos/test-video.mp4"))
|
||||
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -88,7 +89,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
var layer = storyboard.GetLayer("Video");
|
||||
layer.Add(new StoryboardVideo("abc123.mp4", 0));
|
||||
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
mockWorkingBeatmap.As<IWorkingBeatmap>().SetupGet(w => w.Storyboard).Returns(storyboard);
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using ManagedBass;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Models;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
@@ -49,7 +50,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
public void TestNoDelayedHitsounds()
|
||||
{
|
||||
using var resourceStream = TestResources.OpenResource("Samples/hitsound-no-delay.wav");
|
||||
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -96,7 +97,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
Metadata = new BeatmapMetadata { AudioFile = beatmapSet.Files[0].Filename }
|
||||
}
|
||||
};
|
||||
var firstWorking = new Mock<TestWorkingBeatmap>(firstPlayable, null, null);
|
||||
var firstWorking = new Mock<TestWorkingBeatmap>(firstPlayable, null!, null!);
|
||||
firstWorking.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
var secondPlayable = new Beatmap<HitObject>
|
||||
@@ -150,7 +150,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
Metadata = new BeatmapMetadata { AudioFile = beatmapSet.Files[1].Filename }
|
||||
}
|
||||
};
|
||||
var secondWorking = new Mock<TestWorkingBeatmap>(secondPlayable, null, null);
|
||||
var secondWorking = new Mock<TestWorkingBeatmap>(secondPlayable, null!, null!);
|
||||
secondWorking.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
var context = new BeatmapVerifierContext(
|
||||
@@ -165,7 +165,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Linq;
|
||||
using ManagedBass;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
@@ -52,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
beatmap.BeatmapInfo.BeatmapSet.Files.Add(CheckTestHelpers.CreateMockFile("jpg"));
|
||||
|
||||
// Should fail to load, but not produce an error due to the extension not being expected to load.
|
||||
Assert.IsEmpty(check.Run(getContext(null)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(null)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -60,7 +61,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/test-sample.mp3"))
|
||||
{
|
||||
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/blank.wav"))
|
||||
{
|
||||
// This is a 0 ms duration audio file, commonly used to silence sliderslides/ticks, and so should be fine.
|
||||
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +92,13 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
{
|
||||
using (var resourceStream = TestResources.OpenResource("Samples/missing.mp3"))
|
||||
{
|
||||
Assert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(resourceStream)));
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(Stream? resourceStream)
|
||||
{
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
|
||||
return new BeatmapVerifierContext(beatmap, mockWorkingBeatmap.Object);
|
||||
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
var layer = storyboard.GetLayer("Video");
|
||||
layer.Add(new StoryboardVideo("abc123.mp4", 0));
|
||||
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null, null);
|
||||
var mockWorkingBeatmap = new Mock<TestWorkingBeatmap>(beatmap, null!, null!);
|
||||
mockWorkingBeatmap.Setup(w => w.GetStream(It.IsAny<string>())).Returns(resourceStream);
|
||||
mockWorkingBeatmap.As<IWorkingBeatmap>().SetupGet(w => w.Storyboard).Returns(storyboard);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Edit.Checks;
|
||||
@@ -40,7 +41,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestNonZeroBytes()
|
||||
{
|
||||
Assert.IsEmpty(check.Run(getContext(byteLength: 44)));
|
||||
ClassicAssert.IsEmpty(check.Run(getContext(byteLength: 44)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -55,7 +56,7 @@ namespace osu.Game.Tests.Editing.Checks
|
||||
[Test]
|
||||
public void TestMissing()
|
||||
{
|
||||
Assert.IsEmpty(check.Run(getContextMissing()));
|
||||
ClassicAssert.IsEmpty(check.Run(getContextMissing()));
|
||||
}
|
||||
|
||||
private BeatmapVerifierContext getContext(long byteLength)
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Platform;
|
||||
@@ -42,7 +43,7 @@ namespace osu.Game.Tests
|
||||
while (!result()) Thread.Sleep(200);
|
||||
});
|
||||
|
||||
Assert.IsTrue(task.Wait(timeout), failureMessage);
|
||||
ClassicAssert.True(task.Wait(timeout), failureMessage);
|
||||
}
|
||||
|
||||
public partial class TestOsuGameBase : OsuGameBase
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Game.Beatmaps;
|
||||
|
||||
namespace osu.Game.Tests.Localisation
|
||||
@@ -21,8 +22,8 @@ namespace osu.Game.Tests.Localisation
|
||||
};
|
||||
var romanisableString = metadata.GetDisplayTitleRomanisable();
|
||||
|
||||
Assert.AreEqual(metadata.ToString(), romanisableString.Romanised);
|
||||
Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original);
|
||||
ClassicAssert.AreEqual(metadata.ToString(), romanisableString.Romanised);
|
||||
ClassicAssert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Localisation
|
||||
};
|
||||
var romanisableString = metadata.GetDisplayTitleRomanisable();
|
||||
|
||||
Assert.AreEqual(romanisableString.Romanised, romanisableString.Original);
|
||||
ClassicAssert.AreEqual(romanisableString.Romanised, romanisableString.Original);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Online.Rooms;
|
||||
@@ -196,7 +197,7 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0));
|
||||
|
||||
if (isValid)
|
||||
Assert.IsNull(invalid);
|
||||
ClassicAssert.Null(invalid);
|
||||
else
|
||||
Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid));
|
||||
}
|
||||
@@ -214,21 +215,21 @@ namespace osu.Game.Tests.Mods
|
||||
[Test]
|
||||
public void TestFormatScoreMultiplier()
|
||||
{
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0).ToString(), "1.00x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.9999).ToString(), "0.99x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.0).ToString(), "1.00x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.0001).ToString(), "1.01x");
|
||||
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.9).ToString(), "0.90x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.899999999999999).ToString(), "0.90x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.9).ToString(), "0.90x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(0.900000000000001).ToString(), "0.90x");
|
||||
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.1).ToString(), "1.10x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.099999999999999).ToString(), "1.10x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.1).ToString(), "1.10x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.100000000000001).ToString(), "1.10x");
|
||||
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.045).ToString(), "1.05x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.05).ToString(), "1.05x");
|
||||
Assert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.045).ToString(), "1.05x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.05).ToString(), "1.05x");
|
||||
ClassicAssert.AreEqual(ModUtils.FormatScoreMultiplier(1.055).ToString(), "1.06x");
|
||||
}
|
||||
|
||||
private static readonly object[] multiplayer_mod_test_scenarios =
|
||||
@@ -309,7 +310,7 @@ namespace osu.Game.Tests.Mods
|
||||
Assert.That(isValid, Is.EqualTo(scenario.InvalidTypes.Length == 0));
|
||||
|
||||
if (isValid)
|
||||
Assert.IsNull(invalidMods);
|
||||
ClassicAssert.Null(invalidMods);
|
||||
else
|
||||
Assert.That(invalidMods?.Select(t => t.GetType()), Is.EquivalentTo(scenario.InvalidTypes));
|
||||
}
|
||||
@@ -318,12 +319,12 @@ namespace osu.Game.Tests.Mods
|
||||
public void TestPlaylistsModScenarios()
|
||||
{
|
||||
// The rest are tested by TestMultiplayerModScenarios.
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new OsuModHardRock(), false, MatchType.Playlists, false));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new OsuModHardRock(), true, MatchType.Playlists, false));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new OsuModDoubleTime(), false, MatchType.Playlists, false));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new OsuModDoubleTime(), true, MatchType.Playlists, false));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new ModAdaptiveSpeed(), false, MatchType.Playlists, false));
|
||||
Assert.IsTrue(ModUtils.IsValidModForMatch(new ModAdaptiveSpeed(), true, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new OsuModHardRock(), false, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new OsuModHardRock(), true, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new OsuModDoubleTime(), false, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new OsuModDoubleTime(), true, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new ModAdaptiveSpeed(), false, MatchType.Playlists, false));
|
||||
ClassicAssert.True(ModUtils.IsValidModForMatch(new ModAdaptiveSpeed(), true, MatchType.Playlists, false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Legacy;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@@ -54,13 +55,13 @@ namespace osu.Game.Tests.NonVisual
|
||||
// every seventh bar's start time should be at least greater than the whole number we expect.
|
||||
// It cannot be less, as that can affect overlapping scroll algorithms
|
||||
// (the previous timing point might be chosen incorrectly if this is not the case)
|
||||
Assert.GreaterOrEqual(barLine.StartTime, expectedTime);
|
||||
ClassicAssert.GreaterOrEqual(barLine.StartTime, expectedTime);
|
||||
|
||||
// on the other side, make sure we don't stray too far from the expected time either.
|
||||
Assert.IsTrue(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
||||
ClassicAssert.True(Precision.AlmostEquals(barLine.StartTime, expectedTime));
|
||||
|
||||
// check major/minor lines for good measure too
|
||||
Assert.AreEqual(i % signature.Numerator == 0, barLine.Major);
|
||||
ClassicAssert.AreEqual(i % signature.Numerator == 0, barLine.Major);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user