mirror of
https://github.com/ppy/osu
synced 2025-04-01 22:48:33 +00:00
Merge branch 'master' into beatmap-card-basics
This commit is contained in:
commit
cf7545e36c
@ -16,7 +16,7 @@
|
|||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -20,6 +20,7 @@ namespace osu.Android
|
|||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
|
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
|
||||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
|
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
|
||||||
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
Replay = new CatchAutoGenerator(beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
|
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
|
||||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
{
|
{
|
||||||
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
|
||||||
{
|
{
|
||||||
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
|
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
|
||||||
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||||
|
|
||||||
[TestCase(6.5867229481955389d, "diffcalc-test")]
|
[TestCase(6.5295339534769958d, "diffcalc-test")]
|
||||||
[TestCase(1.0416315570967911d, "zero-length-sliders")]
|
[TestCase(1.1514260533755143d, "zero-length-sliders")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
[TestCase(8.2730989071947896d, "diffcalc-test")]
|
[TestCase(9.047752485219954d, "diffcalc-test")]
|
||||||
[TestCase(1.2726413186221039d, "zero-length-sliders")]
|
[TestCase(1.3985711787077566d, "zero-length-sliders")]
|
||||||
public void TestClockRateAdjusted(double expected, string name)
|
public void TestClockRateAdjusted(double expected, string name)
|
||||||
=> Test(expected, name, new OsuModDoubleTime());
|
=> Test(expected, name, new OsuModDoubleTime());
|
||||||
|
|
||||||
|
34
osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
Normal file
34
osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneNoSpinnerStacking : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
|
{
|
||||||
|
var beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
BeatmapInfo = new BeatmapInfo
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
|
||||||
|
Ruleset = ruleset
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 512; i++)
|
||||||
|
{
|
||||||
|
if (i % 32 < 20)
|
||||||
|
beatmap.HitObjects.Add(new Spinner { Position = new Vector2(256, 192), StartTime = i * 200, EndTime = (i * 200) + 100 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
|
@ -12,20 +12,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
{
|
{
|
||||||
public class OsuDifficultyHitObject : DifficultyHitObject
|
public class OsuDifficultyHitObject : DifficultyHitObject
|
||||||
{
|
{
|
||||||
private const int normalized_radius = 52;
|
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
|
||||||
|
private const int min_delta_time = 25;
|
||||||
|
|
||||||
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms to account for simultaneous <see cref="OsuDifficultyHitObject"/>s.
|
|
||||||
/// </summary>
|
|
||||||
public double StrainTime { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double JumpDistance { get; private set; }
|
public double JumpDistance { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Minimum distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
public double MovementDistance { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -37,6 +38,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double? Angle { get; private set; }
|
public double? Angle { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds elapsed since the end time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||||
|
/// </summary>
|
||||||
|
public double MovementTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/> to the end time of the same previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||||
|
/// </summary>
|
||||||
|
public double TravelTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
|
||||||
|
/// </summary>
|
||||||
|
public readonly double StrainTime;
|
||||||
|
|
||||||
private readonly OsuHitObject lastLastObject;
|
private readonly OsuHitObject lastLastObject;
|
||||||
private readonly OsuHitObject lastObject;
|
private readonly OsuHitObject lastObject;
|
||||||
|
|
||||||
@ -46,13 +62,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
this.lastLastObject = (OsuHitObject)lastLastObject;
|
this.lastLastObject = (OsuHitObject)lastLastObject;
|
||||||
this.lastObject = (OsuHitObject)lastObject;
|
this.lastObject = (OsuHitObject)lastObject;
|
||||||
|
|
||||||
setDistances();
|
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
|
||||||
|
StrainTime = Math.Max(DeltaTime, min_delta_time);
|
||||||
|
|
||||||
// Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
|
setDistances(clockRate);
|
||||||
StrainTime = Math.Max(DeltaTime, 25);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDistances()
|
private void setDistances(double clockRate)
|
||||||
{
|
{
|
||||||
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
|
// 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)
|
if (BaseObject is Spinner || lastObject is Spinner)
|
||||||
@ -67,15 +83,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
scalingFactor *= 1 + smallCircleBonus;
|
scalingFactor *= 1 + smallCircleBonus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
||||||
|
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
||||||
|
|
||||||
if (lastObject is Slider lastSlider)
|
if (lastObject is Slider lastSlider)
|
||||||
{
|
{
|
||||||
computeSliderCursorPosition(lastSlider);
|
computeSliderCursorPosition(lastSlider);
|
||||||
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
|
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
|
||||||
|
TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
|
||||||
|
MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
|
||||||
|
|
||||||
|
// Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
|
||||||
|
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
|
||||||
|
|
||||||
|
// For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
|
||||||
|
// such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
|
||||||
|
// In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
|
||||||
|
MovementDistance = Math.Min(JumpDistance, tailJumpDistance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MovementTime = StrainTime;
|
||||||
|
MovementDistance = JumpDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
|
|
||||||
|
|
||||||
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
|
|
||||||
|
|
||||||
if (lastLastObject != null && !(lastLastObject is Spinner))
|
if (lastLastObject != null && !(lastLastObject is Spinner))
|
||||||
{
|
{
|
||||||
@ -98,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
|
|
||||||
slider.LazyEndPosition = slider.StackedPosition;
|
slider.LazyEndPosition = slider.StackedPosition;
|
||||||
|
|
||||||
float approxFollowCircleRadius = (float)(slider.Radius * 3);
|
float followCircleRadius = (float)(slider.Radius * 2.4);
|
||||||
var computeVertex = new Action<double>(t =>
|
var computeVertex = new Action<double>(t =>
|
||||||
{
|
{
|
||||||
double progress = (t - slider.StartTime) / slider.SpanDuration;
|
double progress = (t - slider.StartTime) / slider.SpanDuration;
|
||||||
@ -111,11 +141,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
|||||||
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
|
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
|
||||||
float dist = diff.Length;
|
float dist = diff.Length;
|
||||||
|
|
||||||
if (dist > approxFollowCircleRadius)
|
slider.LazyTravelTime = t - slider.StartTime;
|
||||||
|
|
||||||
|
if (dist > followCircleRadius)
|
||||||
{
|
{
|
||||||
// The cursor would be outside the follow circle, we need to move it
|
// The cursor would be outside the follow circle, we need to move it
|
||||||
diff.Normalize(); // Obtain direction of diff
|
diff.Normalize(); // Obtain direction of diff
|
||||||
dist -= approxFollowCircleRadius;
|
dist -= followCircleRadius;
|
||||||
slider.LazyEndPosition += diff * dist;
|
slider.LazyEndPosition += diff * dist;
|
||||||
slider.LazyTravelDistance += dist;
|
slider.LazyTravelDistance += dist;
|
||||||
}
|
}
|
||||||
|
@ -14,53 +14,96 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Aim : OsuStrainSkill
|
public class Aim : OsuStrainSkill
|
||||||
{
|
{
|
||||||
private const double angle_bonus_begin = Math.PI / 3;
|
|
||||||
private const double timing_threshold = 107;
|
|
||||||
|
|
||||||
public Aim(Mod[] mods)
|
public Aim(Mod[] mods)
|
||||||
: base(mods)
|
: base(mods)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override int HistoryLength => 2;
|
||||||
|
|
||||||
|
private const double wide_angle_multiplier = 1.5;
|
||||||
|
private const double acute_angle_multiplier = 2.0;
|
||||||
|
|
||||||
private double currentStrain = 1;
|
private double currentStrain = 1;
|
||||||
|
|
||||||
private double skillMultiplier => 26.25;
|
private double skillMultiplier => 23.25;
|
||||||
private double strainDecayBase => 0.15;
|
private double strainDecayBase => 0.15;
|
||||||
|
|
||||||
private double strainValueOf(DifficultyHitObject current)
|
private double strainValueOf(DifficultyHitObject current)
|
||||||
{
|
{
|
||||||
if (current.BaseObject is Spinner)
|
if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var osuCurrent = (OsuDifficultyHitObject)current;
|
var osuCurrObj = (OsuDifficultyHitObject)current;
|
||||||
|
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
|
||||||
|
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
|
||||||
|
|
||||||
double aimStrain = 0;
|
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
|
||||||
|
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
|
||||||
|
|
||||||
if (Previous.Count > 0)
|
// 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)
|
||||||
{
|
{
|
||||||
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
|
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
|
||||||
|
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
|
||||||
|
|
||||||
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
|
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
|
||||||
|
}
|
||||||
|
|
||||||
|
// As above, do the same for the previous hitobject.
|
||||||
|
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
|
||||||
|
|
||||||
|
if (osuLastLastObj.BaseObject is Slider)
|
||||||
|
{
|
||||||
|
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
|
||||||
|
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
|
||||||
|
|
||||||
|
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
double angleBonus = 0;
|
||||||
|
double aimStrain = currVelocity; // Start strain with regular velocity.
|
||||||
|
|
||||||
|
if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
|
||||||
|
{
|
||||||
|
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
|
||||||
{
|
{
|
||||||
const double scale = 90;
|
double currAngle = osuCurrObj.Angle.Value;
|
||||||
|
double lastAngle = osuLastObj.Angle.Value;
|
||||||
|
double lastLastAngle = osuLastLastObj.Angle.Value;
|
||||||
|
|
||||||
double angleBonus = Math.Sqrt(
|
// Rewarding angles, take the smaller velocity as base.
|
||||||
Math.Max(osuPrevious.JumpDistance - scale, 0)
|
angleBonus = Math.Min(currVelocity, prevVelocity);
|
||||||
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
|
|
||||||
* Math.Max(osuCurrent.JumpDistance - scale, 0));
|
double wideAngleBonus = calcWideAngleBonus(currAngle);
|
||||||
aimStrain = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
|
double acuteAngleBonus = calcAcuteAngleBonus(currAngle);
|
||||||
|
|
||||||
|
if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
|
||||||
|
acuteAngleBonus = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
|
||||||
|
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
|
||||||
|
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
|
||||||
|
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
|
||||||
|
}
|
||||||
|
|
||||||
|
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
|
||||||
|
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
|
||||||
|
|
||||||
|
angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
|
aimStrain += angleBonus; // Add in angle bonus.
|
||||||
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
|
|
||||||
|
|
||||||
return Math.Max(
|
return aimStrain;
|
||||||
aimStrain + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
|
|
||||||
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2);
|
||||||
|
|
||||||
|
private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);
|
||||||
|
|
||||||
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
|
||||||
|
|
||||||
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
set => StackHeightBindable.Value = value;
|
set => StackHeightBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
|
||||||
|
|
||||||
public double Radius => OBJECT_RADIUS * Scale;
|
public double Radius => OBJECT_RADIUS * Scale;
|
||||||
|
|
||||||
|
@ -79,6 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal float LazyTravelDistance;
|
internal float LazyTravelDistance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The time taken by the cursor upon completion of this <see cref="Slider"/> if it was hit
|
||||||
|
/// with as few movements as possible. This is set and used by difficulty calculation.
|
||||||
|
/// </summary>
|
||||||
|
internal double LazyTravelTime;
|
||||||
|
|
||||||
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
|
@ -8,6 +8,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Judgements;
|
using osu.Game.Rulesets.Osu.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects
|
namespace osu.Game.Rulesets.Osu.Objects
|
||||||
{
|
{
|
||||||
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int MaximumBonusSpins { get; protected set; } = 1;
|
public int MaximumBonusSpins { get; protected set; } = 1;
|
||||||
|
|
||||||
|
public override Vector2 StackOffset => Vector2.Zero;
|
||||||
|
|
||||||
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
|
||||||
{
|
{
|
||||||
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -38,6 +38,15 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu =>
|
||||||
|
{
|
||||||
|
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
|
||||||
|
|
||||||
|
// When the import filename doesn't match, it should be appended (and update the skin.ini).
|
||||||
|
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
|
||||||
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
|
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
@ -199,21 +208,23 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
return zipStream;
|
return zipStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini")
|
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini", bool includeSectionHeader = true)
|
||||||
{
|
{
|
||||||
var zipStream = new MemoryStream();
|
var zipStream = new MemoryStream();
|
||||||
using var zip = ZipArchive.Create();
|
using var zip = ZipArchive.Create();
|
||||||
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique));
|
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique, includeSectionHeader));
|
||||||
zip.SaveTo(zipStream);
|
zip.SaveTo(zipStream);
|
||||||
return zipStream;
|
return zipStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true)
|
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true, bool includeSectionHeader = true)
|
||||||
{
|
{
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
var writer = new StreamWriter(stream);
|
var writer = new StreamWriter(stream);
|
||||||
|
|
||||||
writer.WriteLine("[General]");
|
if (includeSectionHeader)
|
||||||
|
writer.WriteLine("[General]");
|
||||||
|
|
||||||
writer.WriteLine($"Name: {name}");
|
writer.WriteLine($"Name: {name}");
|
||||||
writer.WriteLine($"Author: {author}");
|
writer.WriteLine($"Author: {author}");
|
||||||
|
|
||||||
|
@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
|
||||||
|
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -290,6 +293,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||||
AddUntilStep("wait for join", () => client.Room != null);
|
AddUntilStep("wait for join", () => client.Room != null);
|
||||||
|
|
||||||
|
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
|
||||||
|
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -45,11 +45,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestAddNullUser()
|
public void TestAddUnresolvedUser()
|
||||||
{
|
{
|
||||||
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
|
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
|
||||||
|
|
||||||
AddStep("add non-resolvable user", () => Client.AddNullUser());
|
AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser());
|
||||||
AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
|
AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
|
||||||
|
|
||||||
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
|
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps
|
|||||||
var localRulesetInfo = rulesetInfo as RulesetInfo;
|
var localRulesetInfo = rulesetInfo as RulesetInfo;
|
||||||
|
|
||||||
// Difficulty can only be computed if the beatmap and ruleset are locally available.
|
// Difficulty can only be computed if the beatmap and ruleset are locally available.
|
||||||
if (localBeatmapInfo == null || localRulesetInfo == null)
|
if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null)
|
||||||
{
|
{
|
||||||
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
// If not, fall back to the existing star difficulty (e.g. from an online source).
|
||||||
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));
|
||||||
|
@ -249,6 +249,23 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
|
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
|
||||||
|
|
||||||
|
// Temporary method until this class supports IBeatmapSetInfo or otherwise.
|
||||||
|
public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false)
|
||||||
|
{
|
||||||
|
return beatmapModelDownloader.Download(new BeatmapSetInfo
|
||||||
|
{
|
||||||
|
OnlineBeatmapSetID = model.OnlineID,
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Title = model.Metadata?.Title,
|
||||||
|
Artist = model.Metadata?.Artist,
|
||||||
|
TitleUnicode = model.Metadata?.TitleUnicode,
|
||||||
|
ArtistUnicode = model.Metadata?.ArtistUnicode,
|
||||||
|
Author = new User { Username = model.Metadata?.Author },
|
||||||
|
}
|
||||||
|
}, minimiseDownloadSize);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false)
|
public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false)
|
||||||
{
|
{
|
||||||
return beatmapModelDownloader.Download(model, minimiseDownloadSize);
|
return beatmapModelDownloader.Download(model, minimiseDownloadSize);
|
||||||
|
@ -60,8 +60,9 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
|
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
|
||||||
/// <param name="mods">The mods to show the difficulty with.</param>
|
/// <param name="mods">The mods to show the difficulty with.</param>
|
||||||
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
||||||
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
|
/// <param name="performBackgroundDifficultyLookup">Whether to perform difficulty lookup (including calculation if necessary).</param>
|
||||||
: this(beatmapInfo, shouldShowTooltip)
|
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
|
||||||
|
: this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup)
|
||||||
{
|
{
|
||||||
this.ruleset = ruleset ?? beatmapInfo.Ruleset;
|
this.ruleset = ruleset ?? beatmapInfo.Ruleset;
|
||||||
this.mods = mods ?? Array.Empty<Mod>();
|
this.mods = mods ?? Array.Empty<Mod>();
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override void ParseStreamInto(LineBufferedReader stream, T output)
|
protected override void ParseStreamInto(LineBufferedReader stream, T output)
|
||||||
{
|
{
|
||||||
Section section = Section.None;
|
Section section = Section.General;
|
||||||
|
|
||||||
string line;
|
string line;
|
||||||
|
|
||||||
@ -47,10 +47,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
if (line.StartsWith('[') && line.EndsWith(']'))
|
if (line.StartsWith('[') && line.EndsWith(']'))
|
||||||
{
|
{
|
||||||
if (!Enum.TryParse(line[1..^1], out section))
|
if (!Enum.TryParse(line[1..^1], out section))
|
||||||
{
|
|
||||||
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
|
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
|
||||||
section = Section.None;
|
|
||||||
}
|
|
||||||
|
|
||||||
OnBeginNewSection(section);
|
OnBeginNewSection(section);
|
||||||
continue;
|
continue;
|
||||||
@ -148,7 +145,6 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
protected enum Section
|
protected enum Section
|
||||||
{
|
{
|
||||||
None,
|
|
||||||
General,
|
General,
|
||||||
Editor,
|
Editor,
|
||||||
Metadata,
|
Metadata,
|
||||||
|
@ -107,7 +107,8 @@ namespace osu.Game.Online.API
|
|||||||
WebRequest = CreateWebRequest();
|
WebRequest = CreateWebRequest();
|
||||||
WebRequest.Failed += Fail;
|
WebRequest.Failed += Fail;
|
||||||
WebRequest.AllowRetryOnTimeout = false;
|
WebRequest.AllowRetryOnTimeout = false;
|
||||||
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
if (!string.IsNullOrEmpty(API.AccessToken))
|
||||||
|
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
|
||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public DateTimeOffset Date { get; set; }
|
public DateTimeOffset Date { get; set; }
|
||||||
|
|
||||||
[JsonProperty(@"beatmap")]
|
[JsonProperty(@"beatmap")]
|
||||||
|
[CanBeNull]
|
||||||
public APIBeatmap Beatmap { get; set; }
|
public APIBeatmap Beatmap { get; set; }
|
||||||
|
|
||||||
[JsonProperty("accuracy")]
|
[JsonProperty("accuracy")]
|
||||||
@ -46,6 +47,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
public double? PP { get; set; }
|
public double? PP { get; set; }
|
||||||
|
|
||||||
[JsonProperty(@"beatmapset")]
|
[JsonProperty(@"beatmapset")]
|
||||||
|
[CanBeNull]
|
||||||
public APIBeatmapSet BeatmapSet
|
public APIBeatmapSet BeatmapSet
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
@ -96,7 +98,7 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
{
|
{
|
||||||
TotalScore = TotalScore,
|
TotalScore = TotalScore,
|
||||||
MaxCombo = MaxCombo,
|
MaxCombo = MaxCombo,
|
||||||
BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets),
|
BeatmapInfo = Beatmap?.ToBeatmapInfo(rulesets),
|
||||||
User = User,
|
User = User,
|
||||||
Accuracy = Accuracy,
|
Accuracy = Accuracy,
|
||||||
OnlineScoreID = OnlineID,
|
OnlineScoreID = OnlineID,
|
||||||
|
@ -148,6 +148,10 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
{
|
{
|
||||||
Room = joinedRoom;
|
Room = joinedRoom;
|
||||||
APIRoom = room;
|
APIRoom = room;
|
||||||
|
|
||||||
|
Debug.Assert(LocalUser != null);
|
||||||
|
addUserToAPIRoom(LocalUser);
|
||||||
|
|
||||||
foreach (var user in joinedRoom.Users)
|
foreach (var user in joinedRoom.Users)
|
||||||
updateUserPlayingState(user.UserID, user.State);
|
updateUserPlayingState(user.UserID, user.State);
|
||||||
|
|
||||||
@ -372,6 +376,8 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
Room.Users.Add(user);
|
Room.Users.Add(user);
|
||||||
|
|
||||||
|
addUserToAPIRoom(user);
|
||||||
|
|
||||||
UserJoined?.Invoke(user);
|
UserJoined?.Invoke(user);
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
});
|
});
|
||||||
@ -391,6 +397,18 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
return handleUserLeft(user, UserKicked);
|
return handleUserLeft(user, UserKicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addUserToAPIRoom(MultiplayerRoomUser user)
|
||||||
|
{
|
||||||
|
Debug.Assert(APIRoom != null);
|
||||||
|
|
||||||
|
APIRoom.RecentParticipants.Add(user.User ?? new User
|
||||||
|
{
|
||||||
|
Id = user.UserID,
|
||||||
|
Username = "[Unresolved]"
|
||||||
|
});
|
||||||
|
APIRoom.ParticipantCount.Value++;
|
||||||
|
}
|
||||||
|
|
||||||
private Task handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
|
private Task handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
|
||||||
{
|
{
|
||||||
if (Room == null)
|
if (Room == null)
|
||||||
@ -404,6 +422,10 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
Room.Users.Remove(user);
|
Room.Users.Remove(user);
|
||||||
PlayingUserIds.Remove(user.UserID);
|
PlayingUserIds.Remove(user.UserID);
|
||||||
|
|
||||||
|
Debug.Assert(APIRoom != null);
|
||||||
|
APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID);
|
||||||
|
APIRoom.ParticipantCount.Value--;
|
||||||
|
|
||||||
callback?.Invoke(user);
|
callback?.Invoke(user);
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}, false);
|
}, false);
|
||||||
|
@ -53,7 +53,10 @@ namespace osu.Game.Online.Rooms
|
|||||||
downloadTracker?.RemoveAndDisposeImmediately();
|
downloadTracker?.RemoveAndDisposeImmediately();
|
||||||
|
|
||||||
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
|
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
|
||||||
downloadTracker.State.BindValueChanged(_ => updateAvailability());
|
|
||||||
|
AddInternal(downloadTracker);
|
||||||
|
|
||||||
|
downloadTracker.State.BindValueChanged(_ => updateAvailability(), true);
|
||||||
downloadTracker.Progress.BindValueChanged(_ =>
|
downloadTracker.Progress.BindValueChanged(_ =>
|
||||||
{
|
{
|
||||||
if (downloadTracker.State.Value != DownloadState.Downloading)
|
if (downloadTracker.State.Value != DownloadState.Downloading)
|
||||||
@ -63,9 +66,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
// we don't want to flood the network with this, so rate limit how often we send progress updates.
|
// we don't want to flood the network with this, so rate limit how often we send progress updates.
|
||||||
if (progressUpdate?.Completed != false)
|
if (progressUpdate?.Completed != false)
|
||||||
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
|
||||||
});
|
}, true);
|
||||||
|
|
||||||
AddInternal(downloadTracker);
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System.Net.Http;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.IO.Network;
|
using osu.Framework.IO.Network;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Solo;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Rooms
|
namespace osu.Game.Online.Rooms
|
||||||
@ -14,14 +15,14 @@ namespace osu.Game.Online.Rooms
|
|||||||
private readonly long scoreId;
|
private readonly long scoreId;
|
||||||
private readonly long roomId;
|
private readonly long roomId;
|
||||||
private readonly long playlistItemId;
|
private readonly long playlistItemId;
|
||||||
private readonly ScoreInfo scoreInfo;
|
private readonly SubmittableScore score;
|
||||||
|
|
||||||
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
|
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
|
||||||
{
|
{
|
||||||
this.scoreId = scoreId;
|
this.scoreId = scoreId;
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.playlistItemId = playlistItemId;
|
this.playlistItemId = playlistItemId;
|
||||||
this.scoreInfo = scoreInfo;
|
score = new SubmittableScore(scoreInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override WebRequest CreateWebRequest()
|
protected override WebRequest CreateWebRequest()
|
||||||
@ -31,7 +32,7 @@ namespace osu.Game.Online.Rooms
|
|||||||
req.ContentType = "application/json";
|
req.ContentType = "application/json";
|
||||||
req.Method = HttpMethod.Put;
|
req.Method = HttpMethod.Put;
|
||||||
|
|
||||||
req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings
|
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
}));
|
}));
|
||||||
|
@ -86,7 +86,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideoSetting.Value);
|
beatmaps.Download(beatmapSet, noVideoSetting.Value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
beatmaps.Download(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID }, noVideo);
|
beatmaps.Download(beatmapSet, noVideo);
|
||||||
};
|
};
|
||||||
|
|
||||||
localUser.BindTo(api.LocalUser);
|
localUser.BindTo(api.LocalUser);
|
||||||
|
@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
|
|||||||
{
|
{
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = $"{Score.Beatmap.DifficultyName}",
|
Text = $"{Score.Beatmap?.DifficultyName}",
|
||||||
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
|
||||||
Colour = colours.Yellow
|
Colour = colours.Yellow
|
||||||
},
|
},
|
||||||
|
@ -58,6 +58,11 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
|
return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the difficulty of the beatmap and returns a set of <see cref="TimedDifficultyAttributes"/> representing the difficulty at every relevant time value in the beatmap.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mods">The mods that should be applied to the beatmap.</param>
|
||||||
|
/// <returns>The set of <see cref="TimedDifficultyAttributes"/>.</returns>
|
||||||
public List<TimedDifficultyAttributes> CalculateTimed(params Mod[] mods)
|
public List<TimedDifficultyAttributes> CalculateTimed(params Mod[] mods)
|
||||||
{
|
{
|
||||||
preProcess(mods);
|
preProcess(mods);
|
||||||
@ -77,7 +82,7 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
foreach (var skill in skills)
|
foreach (var skill in skills)
|
||||||
skill.ProcessInternal(hitObject);
|
skill.ProcessInternal(hitObject);
|
||||||
|
|
||||||
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return attribs;
|
return attribs;
|
||||||
|
@ -11,9 +11,21 @@ namespace osu.Game.Rulesets.Difficulty
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimedDifficultyAttributes : IComparable<TimedDifficultyAttributes>
|
public class TimedDifficultyAttributes : IComparable<TimedDifficultyAttributes>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The non-clock-adjusted time value at which the attributes take effect.
|
||||||
|
/// </summary>
|
||||||
public readonly double Time;
|
public readonly double Time;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The attributes.
|
||||||
|
/// </summary>
|
||||||
public readonly DifficultyAttributes Attributes;
|
public readonly DifficultyAttributes Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates new <see cref="TimedDifficultyAttributes"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="time">The non-clock-adjusted time value at which the attributes take effect.</param>
|
||||||
|
/// <param name="attributes">The attributes.</param>
|
||||||
public TimedDifficultyAttributes(double time, DifficultyAttributes attributes)
|
public TimedDifficultyAttributes(double time, DifficultyAttributes attributes)
|
||||||
{
|
{
|
||||||
Time = time;
|
Time = time;
|
||||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Screens.OnlinePlay
|
|||||||
|
|
||||||
private void refresh()
|
private void refresh()
|
||||||
{
|
{
|
||||||
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
|
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) };
|
||||||
|
|
||||||
beatmapText.Clear();
|
beatmapText.Clear();
|
||||||
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
|
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
|
||||||
|
@ -194,68 +194,61 @@ namespace osu.Game.Skinning
|
|||||||
string nameLine = @$"Name: {item.Name}";
|
string nameLine = @$"Name: {item.Name}";
|
||||||
string authorLine = @$"Author: {item.Creator}";
|
string authorLine = @$"Author: {item.Creator}";
|
||||||
|
|
||||||
|
string[] newLines =
|
||||||
|
{
|
||||||
|
@"// The following content was automatically added by osu! during import, based on filename / folder metadata.",
|
||||||
|
@"[General]",
|
||||||
|
nameLine,
|
||||||
|
authorLine,
|
||||||
|
};
|
||||||
|
|
||||||
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (existingFile != null)
|
if (existingFile == null)
|
||||||
{
|
{
|
||||||
List<string> outputLines = new List<string>();
|
// In the case a skin doesn't have a skin.ini yet, let's create one.
|
||||||
|
writeNewSkinIni();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool addedName = false;
|
using (Stream stream = new MemoryStream())
|
||||||
bool addedAuthor = false;
|
{
|
||||||
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
using (var stream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath))
|
|
||||||
using (var sr = new StreamReader(stream))
|
|
||||||
{
|
{
|
||||||
string line;
|
using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath))
|
||||||
|
using (var sr = new StreamReader(existingStream))
|
||||||
while ((line = sr.ReadLine()) != null)
|
|
||||||
{
|
{
|
||||||
if (line.StartsWith(@"Name:", StringComparison.Ordinal))
|
string line;
|
||||||
{
|
while ((line = sr.ReadLine()) != null)
|
||||||
outputLines.Add(nameLine);
|
|
||||||
addedName = true;
|
|
||||||
}
|
|
||||||
else if (line.StartsWith(@"Author:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
outputLines.Add(authorLine);
|
|
||||||
addedAuthor = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
outputLines.Add(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!addedName || !addedAuthor)
|
|
||||||
{
|
|
||||||
outputLines.AddRange(new[]
|
|
||||||
{
|
|
||||||
@"[General]",
|
|
||||||
nameLine,
|
|
||||||
authorLine,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
using (Stream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
|
||||||
{
|
|
||||||
foreach (string line in outputLines)
|
|
||||||
sw.WriteLine(line);
|
sw.WriteLine(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaceFile(item, existingFile, stream);
|
sw.WriteLine();
|
||||||
|
|
||||||
|
foreach (string line in newLines)
|
||||||
|
sw.WriteLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReplaceFile(item, existingFile, stream);
|
||||||
|
|
||||||
|
// can be removed 20220502.
|
||||||
|
if (!ensureIniWasUpdated(item))
|
||||||
|
{
|
||||||
|
Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important);
|
||||||
|
|
||||||
|
DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)));
|
||||||
|
writeNewSkinIni();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
void writeNewSkinIni()
|
||||||
{
|
{
|
||||||
using (Stream stream = new MemoryStream())
|
using (Stream stream = new MemoryStream())
|
||||||
{
|
{
|
||||||
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
|
||||||
{
|
{
|
||||||
sw.WriteLine(@"[General]");
|
foreach (string line in newLines)
|
||||||
sw.WriteLine(nameLine);
|
sw.WriteLine(line);
|
||||||
sw.WriteLine(authorLine);
|
|
||||||
sw.WriteLine(@"Version: latest");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AddFile(item, stream, @"skin.ini");
|
AddFile(item, stream, @"skin.ini");
|
||||||
@ -263,6 +256,17 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ensureIniWasUpdated(SkinInfo item)
|
||||||
|
{
|
||||||
|
// This is a final consistency check to ensure that hash computation doesn't enter an infinite loop.
|
||||||
|
// With other changes to the surrounding code this should never be hit, but until we are 101% sure that there
|
||||||
|
// are no other cases let's avoid a hard startup crash by bailing and alerting.
|
||||||
|
|
||||||
|
var instance = GetSkin(item);
|
||||||
|
|
||||||
|
return instance.Configuration.SkinInfo.Name == item.Name;
|
||||||
|
}
|
||||||
|
|
||||||
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var instance = GetSkin(model);
|
var instance = GetSkin(model);
|
||||||
|
@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return roomUser;
|
return roomUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
public void TestAddUnresolvedUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.UNRESOLVED_USER_ID));
|
||||||
|
|
||||||
private void addUser(MultiplayerRoomUser user)
|
private void addUser(MultiplayerRoomUser user)
|
||||||
{
|
{
|
||||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual
|
|||||||
/// A special user ID which <see cref="ComputeValueAsync"/> would return a <see langword="null"/> <see cref="User"/> for.
|
/// A special user ID which <see cref="ComputeValueAsync"/> would return a <see langword="null"/> <see cref="User"/> for.
|
||||||
/// As a simulation to what a regular <see cref="UserLookupCache"/> would return in the case of failing to fetch the user.
|
/// As a simulation to what a regular <see cref="UserLookupCache"/> would return in the case of failing to fetch the user.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int NULL_USER_ID = -1;
|
public const int UNRESOLVED_USER_ID = -1;
|
||||||
|
|
||||||
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
protected override Task<User> ComputeValueAsync(int lookup, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (lookup == NULL_USER_ID)
|
if (lookup == UNRESOLVED_USER_ID)
|
||||||
return Task.FromResult((User)null);
|
return Task.FromResult((User)null);
|
||||||
|
|
||||||
return Task.FromResult(new User
|
return Task.FromResult(new User
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.37" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.11.37" />
|
||||||
<PackageReference Include="Humanizer" Version="2.11.10" />
|
<PackageReference Include="Humanizer" Version="2.11.10" />
|
||||||
<PackageReference Include="MessagePack" Version="2.3.85" />
|
<PackageReference Include="MessagePack" Version="2.3.85" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.10" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
@ -38,8 +38,8 @@
|
|||||||
<PackageReference Include="Realm" Version="10.6.0" />
|
<PackageReference Include="Realm" Version="10.6.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
|
||||||
<PackageReference Include="Sentry" Version="3.9.4" />
|
<PackageReference Include="Sentry" Version="3.10.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.29.0" />
|
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2021.1029.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.28.3" />
|
<PackageReference Include="SharpCompress" Version="0.30.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||||
|
Loading…
Reference in New Issue
Block a user